import { OperatorTypes } from '@unito/integration-api'; import express from 'express'; import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import extractFilters from '../../src/middlewares/filters.js'; describe('filters middleware', () => { it('properly parse operators', () => { Object.values(OperatorTypes).forEach(operator => { const request = { query: { filter: `aKey${operator}value` }, } as express.Request; const response = { locals: {} } as express.Response; extractFilters(request, response, () => {}); assert.deepEqual(response.locals, { filters: [{ field: 'aKey', operator, values: ['value'] }], }); }); const request = { query: { filter: `aKey!!` }, } as express.Request; const response = { locals: {} } as express.Response; extractFilters(request, response, () => {}); assert.deepEqual(response.locals, { filters: [{ field: 'aKey', operator: OperatorTypes.IS_NULL, values: [] }], }); }); it('decodes URI components', () => { const request = { query: { filter: 'status=foo%2Cbar%21%21%2C%3Fbaz%3D!%3Equx' } } as express.Request< any, object, object, { filter: string } >; const response = { locals: {} } as express.Response; extractFilters(request, response, () => {}); assert.deepEqual(response.locals, { filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }], }); }); it('parses multi-value filters separated by pipe', () => { const request = { query: { filter: 'status=/statuses/Done|/statuses/InProgress' }, } as express.Request; const response = { locals: {} } as express.Response; extractFilters(request, response, () => {}); assert.deepEqual(response.locals, { filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['/statuses/Done', '/statuses/InProgress'] }], }); }); it('preserves base64 padding (=) in filter values and does not lose subsequent pipe-separated values', () => { // Base64 values end with == which contains the = operator character. // split('=', 2) incorrectly truncates after the second = in the string, // discarding the | separator and subsequent values. const request = { query: { filter: 'category=/choices/QnVzaW5lc3MgU3lzdGVtOi06QnVzaW5lc3MgU3lzdGVt|/choices/QXBwbGljYXRpb246LTpBcHBsaWNhdGlvbg==,' + 'subcategory=/choices/U0FQOi06QnVzaW5lc3MgU3lzdGVtX1NBUA==|/choices/UmVkd29vZCBSTUo6LTpyZWR3b29kIHJtag==', }, } as express.Request; const response = { locals: {} } as express.Response; extractFilters(request, response, () => {}); assert.deepEqual(response.locals, { filters: [ { field: 'category', operator: OperatorTypes.EQUAL, values: [ '/choices/QnVzaW5lc3MgU3lzdGVtOi06QnVzaW5lc3MgU3lzdGVt', '/choices/QXBwbGljYXRpb246LTpBcHBsaWNhdGlvbg==', ], }, { field: 'subcategory', operator: OperatorTypes.EQUAL, values: ['/choices/U0FQOi06QnVzaW5lc3MgU3lzdGVtX1NBUA==', '/choices/UmVkd29vZCBSTUo6LTpyZWR3b29kIHJtag=='], }, ], }); }); it('matches the earliest operator even when operator characters appear in the value', () => { const request = { query: { filter: 'description=contains>=text' }, } as express.Request; const response = { locals: {} } as express.Response; extractFilters(request, response, () => {}); assert.deepEqual(response.locals, { filters: [{ field: 'description', operator: OperatorTypes.EQUAL, values: ['contains>=text'] }], }); }); it('no data', () => { const request = { query: {} } as express.Request; const response = { locals: {} } as express.Response; extractFilters(request, response, () => {}); assert.deepEqual(response.locals, { filters: [], }); }); });