484 lines
11 KiB
TypeScript
484 lines
11 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
|
|
import { FilterJSON } from "./filter_json";
|
|
import type { FilterData } from "./types";
|
|
|
|
interface FilterMarshalTestCase {
|
|
name: string;
|
|
filter: FilterData;
|
|
expected: string;
|
|
}
|
|
|
|
interface FilterUnmarshalTestCase {
|
|
name: string;
|
|
input: string;
|
|
expected: FilterData;
|
|
}
|
|
|
|
interface FilterRoundTripTestCase {
|
|
name: string;
|
|
filter: FilterData;
|
|
}
|
|
|
|
const marshalTestCases: FilterMarshalTestCase[] = [
|
|
{
|
|
name: "empty filter",
|
|
filter: {},
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "undefined IDs",
|
|
filter: { ids: undefined },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "null IDs",
|
|
filter: { ids: null },
|
|
expected: '{"ids": null}',
|
|
},
|
|
{
|
|
name: "empty IDs",
|
|
filter: { ids: [] },
|
|
expected: '{"ids":[]}',
|
|
},
|
|
{
|
|
name: "populated IDs",
|
|
filter: { ids: ["abc", "123"] },
|
|
expected: '{"ids":["abc","123"]}',
|
|
},
|
|
{
|
|
name: "undefined Authors",
|
|
filter: { authors: undefined },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "null Authors",
|
|
filter: { authors: null },
|
|
expected: '{"authors": null}',
|
|
},
|
|
{
|
|
name: "empty Authors",
|
|
filter: { authors: [] },
|
|
expected: '{"authors":[]}',
|
|
},
|
|
{
|
|
name: "populated Authors",
|
|
filter: { authors: ["abc", "123"] },
|
|
expected: '{"authors":["abc","123"]}',
|
|
},
|
|
{
|
|
name: "undefined Kinds",
|
|
filter: { kinds: undefined },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "null Kinds",
|
|
filter: { kinds: null },
|
|
expected: '{"kinds": null}',
|
|
},
|
|
{
|
|
name: "empty Kinds",
|
|
filter: { kinds: [] },
|
|
expected: '{"kinds":[]}',
|
|
},
|
|
{
|
|
name: "populated Kinds",
|
|
filter: { kinds: [1, 20001] },
|
|
expected: '{"kinds":[1,20001]}',
|
|
},
|
|
{
|
|
name: "undefined Since",
|
|
filter: { since: undefined },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "null Since",
|
|
filter: { since: null },
|
|
expected: '{"since": null}',
|
|
},
|
|
{
|
|
name: "populated Since",
|
|
filter: { since: 1000 },
|
|
expected: '{"since":1000}',
|
|
},
|
|
{
|
|
name: "undefined Until",
|
|
filter: { until: undefined },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "null Until",
|
|
filter: { until: null },
|
|
expected: '{"until": null}',
|
|
},
|
|
{
|
|
name: "populated Until",
|
|
filter: { until: 1000 },
|
|
expected: '{"until":1000}',
|
|
},
|
|
{
|
|
name: "undefined Limit",
|
|
filter: { limit: undefined },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "null Limit",
|
|
filter: { limit: null },
|
|
expected: '{"limit": null}',
|
|
},
|
|
{
|
|
name: "populated Limit",
|
|
filter: { limit: 100 },
|
|
expected: '{"limit":100}',
|
|
},
|
|
{
|
|
name: "all standard fields",
|
|
filter: {
|
|
ids: ["abc", "123"],
|
|
authors: ["def", "456"],
|
|
kinds: [1, 200, 3000],
|
|
since: 1000,
|
|
until: 2000,
|
|
limit: 100,
|
|
},
|
|
expected:
|
|
'{"ids":["abc","123"],"authors":["def","456"],"kinds":[1,200,3000],"since":1000,"until":2000,"limit":100}',
|
|
},
|
|
{
|
|
name: "mixed fields",
|
|
filter: { ids: undefined, authors: [], kinds: [1] },
|
|
expected: '{"authors":[],"kinds":[1]}',
|
|
},
|
|
{
|
|
name: "undefined tags map",
|
|
filter: { tags: undefined },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "null tags map",
|
|
filter: { tags: null },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "single-letter tag",
|
|
filter: { tags: { e: ["event1"] } },
|
|
expected: '{"#e":["event1"]}',
|
|
},
|
|
{
|
|
name: "multi-letter tag",
|
|
filter: { tags: { emoji: ["🔥", "💧"] } },
|
|
expected: '{"#emoji":["🔥","💧"]}',
|
|
},
|
|
{
|
|
name: "null tag array",
|
|
filter: { tags: { p: null } },
|
|
expected: '{"#p":null}',
|
|
},
|
|
{
|
|
name: "empty tag array",
|
|
filter: { tags: { p: [] } },
|
|
expected: '{"#p":[]}',
|
|
},
|
|
{
|
|
name: "multiple tags",
|
|
filter: {
|
|
tags: {
|
|
e: ["event1", "event2"],
|
|
p: ["pubkey1", "pubkey2"],
|
|
},
|
|
},
|
|
expected: '{"#e":["event1","event2"],"#p":["pubkey1","pubkey2"]}',
|
|
},
|
|
{
|
|
name: "simple extension",
|
|
filter: { extensions: { search: "query" } },
|
|
expected: '{"search":"query"}',
|
|
},
|
|
{
|
|
name: "extension with nested object",
|
|
filter: { extensions: { meta: { author: "alice", score: 99 } } },
|
|
expected: '{"meta":{"author":"alice","score":99}}',
|
|
},
|
|
{
|
|
name: "extension with nested array",
|
|
filter: { extensions: { items: [1, 2, 3] } },
|
|
expected: '{"items":[1,2,3]}',
|
|
},
|
|
{
|
|
name: "extension with complex nested structure",
|
|
filter: { extensions: { data: { users: [{ id: 1 }], count: 5 } } },
|
|
expected: '{"data":{"users":[{"id":1}],"count":5}}',
|
|
},
|
|
{
|
|
name: "multiple extensions",
|
|
filter: { extensions: { search: "x", depth: 3 } },
|
|
expected: '{"search":"x","depth":3}',
|
|
},
|
|
{
|
|
name: "extension collides with standard field - IDs",
|
|
filter: { ids: ["real"], extensions: { ids: ["fake"] } },
|
|
expected: '{"ids":["real"]}',
|
|
},
|
|
{
|
|
name: "extension collides with standard field - Since",
|
|
filter: { since: 100, extensions: { since: 999 } },
|
|
expected: '{"since":100}',
|
|
},
|
|
{
|
|
name: "extension collides with multiple standard fields",
|
|
filter: {
|
|
authors: ["a"],
|
|
kinds: [1],
|
|
extensions: { authors: ["b"], kinds: [2] },
|
|
},
|
|
expected: '{"authors":["a"],"kinds":[1]}',
|
|
},
|
|
{
|
|
name: "extension collides with tag field - #e",
|
|
filter: { extensions: { "#e": ["fakeevent"] } },
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "extension collides with standard and tag fields",
|
|
filter: {
|
|
authors: ["realauthor"],
|
|
tags: { e: ["realevent"] },
|
|
extensions: { authors: ["fakeauthor"], "#e": ["fakeevent"] },
|
|
},
|
|
expected: '{"authors":["realauthor"],"#e":["realevent"]}',
|
|
},
|
|
{
|
|
name: "filter with all field types",
|
|
filter: {
|
|
ids: ["x"],
|
|
since: 100,
|
|
tags: { e: ["y"] },
|
|
extensions: { search: "z", ids: ["fakeid"] },
|
|
},
|
|
expected: '{"ids":["x"],"since":100,"#e":["y"],"search":"z"}',
|
|
},
|
|
];
|
|
|
|
const unmarshalTestCases: FilterUnmarshalTestCase[] = [
|
|
{
|
|
name: "empty object",
|
|
input: "{}",
|
|
expected: {},
|
|
},
|
|
{
|
|
name: "null IDs",
|
|
input: '{"ids": null}',
|
|
expected: { ids: null },
|
|
},
|
|
{
|
|
name: "empty IDs",
|
|
input: '{"ids": []}',
|
|
expected: { ids: [] },
|
|
},
|
|
{
|
|
name: "populated IDs",
|
|
input: '{"ids": ["abc","123"]}',
|
|
expected: { ids: ["abc", "123"] },
|
|
},
|
|
{
|
|
name: "null Authors",
|
|
input: '{"authors": null}',
|
|
expected: { authors: null },
|
|
},
|
|
{
|
|
name: "empty Authors",
|
|
input: '{"authors": []}',
|
|
expected: { authors: [] },
|
|
},
|
|
{
|
|
name: "populated Authors",
|
|
input: '{"authors": ["abc","123"]}',
|
|
expected: { authors: ["abc", "123"] },
|
|
},
|
|
{
|
|
name: "null Kinds",
|
|
input: '{"kinds": null}',
|
|
expected: { kinds: null },
|
|
},
|
|
{
|
|
name: "empty Kinds",
|
|
input: '{"kinds": []}',
|
|
expected: { kinds: [] },
|
|
},
|
|
{
|
|
name: "populated Kinds",
|
|
input: '{"kinds": [1,2,3]}',
|
|
expected: { kinds: [1, 2, 3] },
|
|
},
|
|
{
|
|
name: "null Since",
|
|
input: '{"since": null}',
|
|
expected: { since: undefined },
|
|
},
|
|
{
|
|
name: "populated Since",
|
|
input: '{"since": 1000}',
|
|
expected: { since: 1000 },
|
|
},
|
|
{
|
|
name: "null Until",
|
|
input: '{"until": null}',
|
|
expected: { until: undefined },
|
|
},
|
|
{
|
|
name: "populated Until",
|
|
input: '{"until": 1000}',
|
|
expected: { until: 1000 },
|
|
},
|
|
{
|
|
name: "null Limit",
|
|
input: '{"limit": null}',
|
|
expected: { limit: undefined },
|
|
},
|
|
{
|
|
name: "populated Limit",
|
|
input: '{"limit": 1000}',
|
|
expected: { limit: 1000 },
|
|
},
|
|
{
|
|
name: "all standard fields",
|
|
input:
|
|
'{"ids":["abc","123"],"authors":["def","456"],"kinds":[1,200,3000],"since":1000,"until":2000,"limit":100}',
|
|
expected: {
|
|
ids: ["abc", "123"],
|
|
authors: ["def", "456"],
|
|
kinds: [1, 200, 3000],
|
|
since: 1000,
|
|
until: 2000,
|
|
limit: 100,
|
|
},
|
|
},
|
|
{
|
|
name: "mixed fields",
|
|
input: '{"ids": null, "authors": [], "kinds": [1]}',
|
|
expected: { ids: null, authors: [], kinds: [1] },
|
|
},
|
|
{
|
|
name: "zero int pointers",
|
|
input: '{"since": 0, "until": 0, "limit": 0}',
|
|
expected: { since: 0, until: 0, limit: 0 },
|
|
},
|
|
{
|
|
name: "single-letter tag",
|
|
input: '{"#e":["event1"]}',
|
|
expected: { tags: { e: ["event1"] } },
|
|
},
|
|
{
|
|
name: "multi-letter tag",
|
|
input: '{"#emoji":["🔥","💧"]}',
|
|
expected: { tags: { emoji: ["🔥", "💧"] } },
|
|
},
|
|
{
|
|
name: "empty tag array",
|
|
input: '{"#p":[]}',
|
|
expected: { tags: { p: [] } },
|
|
},
|
|
{
|
|
name: "multiple tags",
|
|
input: '{"#p":["pubkey1","pubkey2"],"#e":["event1","event2"]}',
|
|
expected: {
|
|
tags: {
|
|
p: ["pubkey1", "pubkey2"],
|
|
e: ["event1", "event2"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "null tag",
|
|
input: '{"#p":null}',
|
|
expected: { tags: { p: null } },
|
|
},
|
|
{
|
|
name: "simple extension",
|
|
input: '{"search":"query"}',
|
|
expected: { extensions: { search: "query" } },
|
|
},
|
|
{
|
|
name: "extension with nested object",
|
|
input: '{"meta":{"author":"alice","score":99}}',
|
|
expected: { extensions: { meta: { author: "alice", score: 99 } } },
|
|
},
|
|
{
|
|
name: "extension with nested array",
|
|
input: '{"items":[1,2,3]}',
|
|
expected: { extensions: { items: [1, 2, 3] } },
|
|
},
|
|
{
|
|
name: "extension with complex nested structure",
|
|
input: '{"data":{"level1":{"level2":[{"id":1}]}}}',
|
|
expected: { extensions: { data: { level1: { level2: [{ id: 1 }] } } } },
|
|
},
|
|
{
|
|
name: "multiple extensions",
|
|
input: '{"search":"x","custom":true,"depth":3}',
|
|
expected: { extensions: { search: "x", custom: true, depth: 3 } },
|
|
},
|
|
{
|
|
name: "extension with null value",
|
|
input: '{"optional":null}',
|
|
expected: { extensions: { optional: null } },
|
|
},
|
|
{
|
|
name: "kitchen sink",
|
|
input: '{"ids":["x"],"since":100,"#e":["y"],"search":"z"}',
|
|
expected: {
|
|
ids: ["x"],
|
|
since: 100,
|
|
tags: { e: ["y"] },
|
|
extensions: { search: "z" },
|
|
},
|
|
},
|
|
];
|
|
|
|
const roundTripTestCases: FilterRoundTripTestCase[] = [
|
|
{
|
|
name: "fully populated filter",
|
|
filter: {
|
|
ids: ["x"],
|
|
since: 100,
|
|
tags: { e: ["y"] },
|
|
extensions: { search: "z" },
|
|
},
|
|
},
|
|
];
|
|
|
|
describe("FilterJSON.toJSON", () => {
|
|
test.each(marshalTestCases)("$name", ({ filter, expected }) => {
|
|
const result = JSON.stringify(FilterJSON.toJSON(filter));
|
|
const expectedObj = JSON.parse(expected);
|
|
const actualObj = JSON.parse(result);
|
|
expect(actualObj).toEqual(expectedObj);
|
|
});
|
|
});
|
|
|
|
describe("FilterJSON.fromJSON", () => {
|
|
test.each(unmarshalTestCases)("$name", ({ input, expected }) => {
|
|
const result = FilterJSON.fromJSON(JSON.parse(input));
|
|
expectEqualFilters(result, expected);
|
|
});
|
|
});
|
|
|
|
describe("FilterJSON round trip", () => {
|
|
test.each(roundTripTestCases)("$name", ({ filter }) => {
|
|
const jsonBytes = JSON.stringify(FilterJSON.toJSON(filter));
|
|
const result = FilterJSON.fromJSON(JSON.parse(jsonBytes));
|
|
expectEqualFilters(result, filter);
|
|
});
|
|
});
|
|
|
|
function expectEqualFilters(got: FilterData, want: FilterData): void {
|
|
expect(got.ids).toEqual(want.ids);
|
|
expect(got.authors).toEqual(want.authors);
|
|
expect(got.kinds).toEqual(want.kinds);
|
|
expect(got.since).toEqual(want.since);
|
|
expect(got.until).toEqual(want.until);
|
|
expect(got.limit).toEqual(want.limit);
|
|
expect(got.tags).toEqual(want.tags);
|
|
expect(got.extensions).toEqual(want.extensions);
|
|
}
|