From 23906f5a4a66ecd1d8d3c4b36a46151e9eea6863 Mon Sep 17 00:00:00 2001 From: karilint Date: Thu, 21 May 2026 14:38:11 +0300 Subject: [PATCH 1/3] Add cross-search export content tests --- .../src/api-tests/crossSearch/export.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/backend/src/api-tests/crossSearch/export.test.ts b/backend/src/api-tests/crossSearch/export.test.ts index 1ef773b5..7d29ff62 100644 --- a/backend/src/api-tests/crossSearch/export.test.ts +++ b/backend/src/api-tests/crossSearch/export.test.ts @@ -1,7 +1,45 @@ import { beforeAll, afterAll, describe, it, expect } from '@jest/globals' +import { parseString } from 'fast-csv' +import request from 'supertest' +import type { Response } from 'superagent' +import app from '../../app' import { resetDatabase, send, resetDatabaseTimeout } from '../utils' import { pool } from '../../utils/db' +type ResponseStream = { + on: (event: 'data', handler: (chunk: Buffer) => void) => void +} & { + on: (event: 'end', handler: () => void) => void +} + +type CsvRow = Record + +const parseResponseBody = (res: Response, callback: (err: Error | null, body: Buffer) => void) => { + const data: Buffer[] = [] + const stream = res as unknown as ResponseStream + stream.on('data', chunk => data.push(chunk)) + stream.on('end', () => { + callback(null, Buffer.concat(data)) + }) +} + +const parseCsvRows = async (csvContent: string) => + new Promise((resolve, reject) => { + const rows: CsvRow[] = [] + parseString(csvContent, { headers: true }) + .on('error', reject) + .on('data', (row: CsvRow) => rows.push(row)) + .on('end', () => resolve(rows)) + }) + +const exportCrossSearchCsv = async (body: { columnFilters: unknown[]; sorting: unknown[] }) => { + const response = await request(app).post('/crosssearch/export').send(body).buffer(true).parse(parseResponseBody) + return { + ...response, + csvContent: (response.body as Buffer).toString('utf8'), + } +} + describe('Getting cross-search export', () => { beforeAll(async () => { await resetDatabase() @@ -51,6 +89,41 @@ describe('Getting cross-search export', () => { expect(response.body).toEqual({ error: 'Sorting is not an array.' }) }) + it('exports filtered CSV content with expected headers and rows', async () => { + const response = await exportCrossSearchCsv({ + columnFilters: [{ id: 'country', value: 'Spain' }], + sorting: [{ id: 'loc_name', desc: false }], + }) + + expect(response.status).toEqual(200) + expect(response.headers['content-type']).toMatch(/text\/csv/i) + expect(response.headers['content-disposition']).toMatch(/attachment;\s*filename="cross_search/i) + expect(response.csvContent).toContain('"lid_now_loc"') + expect(response.csvContent).toContain('"loc_name"') + expect(response.csvContent).toContain('"country"') + expect(response.csvContent).toContain('"species_name"') + expect(response.csvContent).not.toContain('"full_count"') + + const rows = await parseCsvRows(response.csvContent) + expect(rows).toHaveLength(10) + expect(rows.every(row => row.country === 'Spain')).toBe(true) + expect(rows.map(row => row.loc_name)).toContain("Romanyà d'Empordà") + expect(rows.map(row => row.species_name)).toContain('dubia') + }) + + it('exports CSV content in the requested sorting order', async () => { + const response = await exportCrossSearchCsv({ + columnFilters: [], + sorting: [{ id: 'lid_now_loc', desc: true }], + }) + + expect(response.status).toEqual(200) + const rows = await parseCsvRows(response.csvContent) + expect(rows).toHaveLength(20) + expect(rows.slice(0, 5).map(row => row.lid_now_loc)).toEqual(['28518', '28518', '28518', '28518', '28518']) + expect(rows.slice(5, 10).map(row => row.lid_now_loc)).toEqual(['24797', '24797', '24797', '24797', '24797']) + }) + it('with invalid sorting content does not work', async () => { const { body: responseBody1, status: responseStatus1 } = await send( `crosssearch/export/[]/[{"id": "not_a_column", "desc": true}]`, From b4c81acc88306348f29c1748d19185228907b60b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 12:13:37 +0000 Subject: [PATCH 2/3] Complete PR review request Agent-Logs-Url: https://github.com/nowcommunity/nowdatabase/sessions/fca3e4df-2a68-4f69-b9e8-b84af5d961e9 --- frontend/package-lock.json | 68 -------------------------------------- 1 file changed, 68 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a37c3f0b..a80a9a66 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2264,19 +2264,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -11282,31 +11269,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11669,36 +11631,6 @@ "node": ">=6" } }, - "node_modules/terser": { - "version": "5.34.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", - "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", From 5b62f18d932527064286b6ea7a9764934ee66c6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 12:15:36 +0000 Subject: [PATCH 3/3] Revert unintended lockfile change Agent-Logs-Url: https://github.com/nowcommunity/nowdatabase/sessions/fca3e4df-2a68-4f69-b9e8-b84af5d961e9 --- frontend/package-lock.json | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a80a9a66..a37c3f0b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2264,6 +2264,19 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -11269,6 +11282,31 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11631,6 +11669,36 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",