Highly ambitious ATProtocol AppView service and sdks
1/**
2 * Tests for AT Protocol Lexicon Validation Library
3 *
4 * Comprehensive test suite covering all validation functionality including
5 * WASM function tests, lexicon validation, record validation, and resource management.
6 */
7
8import {
9 LexiconValidator,
10 ValidationError,
11 validate,
12 validateRecord,
13 validateStringFormat,
14 validateWithDetails,
15 withValidator,
16 isValidNsid,
17 type LexiconDoc,
18} from "./lexicon.ts";
19
20// ============================================================================
21// Test Data
22// ============================================================================
23
24const validLexicon: LexiconDoc = {
25 id: "com.example.post",
26 lexicon: 1,
27 defs: {
28 main: {
29 type: "record",
30 key: "tid",
31 record: {
32 type: "object",
33 required: ["text"],
34 properties: {
35 text: { type: "string", maxLength: 300 },
36 author: { type: "string", format: "did" },
37 },
38 },
39 },
40 },
41};
42
43const lexiconWithBlob: LexiconDoc = {
44 id: "com.example.profile",
45 lexicon: 1,
46 defs: {
47 main: {
48 type: "record",
49 key: "tid",
50 record: {
51 type: "object",
52 properties: {
53 avatar: { type: "blob" },
54 displayName: { type: "string" },
55 },
56 },
57 },
58 },
59};
60
61const lexiconWithLocalRefs: LexiconDoc = {
62 id: "com.example.refs",
63 lexicon: 1,
64 defs: {
65 main: {
66 type: "record",
67 key: "tid",
68 record: {
69 type: "object",
70 properties: {
71 image: { type: "ref", ref: "#imageRef" },
72 },
73 },
74 },
75 imageRef: {
76 type: "object",
77 properties: {
78 url: { type: "string" },
79 },
80 },
81 },
82};
83
84const invalidLexicon: LexiconDoc = {
85 id: "com.example.broken",
86 lexicon: 1,
87 defs: {
88 main: {
89 type: "record",
90 key: "tid",
91 // missing required "record" field
92 },
93 },
94};
95
96// ============================================================================
97// Basic WASM Function Tests
98// ============================================================================
99
100Deno.test("isValidNsid - valid NSID", async () => {
101 const result = await isValidNsid("com.example.post");
102 if (result !== true) throw new Error(`Expected true, got ${result}`);
103});
104
105Deno.test("isValidNsid - invalid NSID", async () => {
106 const result = await isValidNsid("com.example");
107 if (result !== false) throw new Error(`Expected false, got ${result}`);
108});
109
110Deno.test("validateStringFormat - valid DID", async () => {
111 await validateStringFormat("did:plc:example123", "did");
112});
113
114Deno.test("validateStringFormat - invalid DID", async () => {
115 let threw = false;
116 try {
117 await validateStringFormat("not-a-did", "did");
118 } catch (error) {
119 threw = true;
120 if (!(error instanceof ValidationError)) {
121 throw new Error("Should throw ValidationError");
122 }
123 }
124 if (!threw) throw new Error("Should have thrown for invalid DID");
125});
126
127// ============================================================================
128// Lexicon Validation Tests
129// ============================================================================
130
131Deno.test("validate - valid lexicon", async () => {
132 const result = await validate([validLexicon]);
133 if (result !== null) throw new Error("Valid lexicon should return null");
134});
135
136Deno.test("validate - invalid lexicon", async () => {
137 const result = await validate([invalidLexicon]);
138 if (result === null) throw new Error("Invalid lexicon should return errors");
139 if (!("com.example.broken" in result))
140 throw new Error("Should contain broken lexicon ID");
141 if (!Array.isArray(result["com.example.broken"]))
142 throw new Error("Errors should be array");
143});
144
145Deno.test("validate - blob type support", async () => {
146 const result = await validate([lexiconWithBlob]);
147 if (result !== null)
148 throw new Error("Lexicon with blob types should be valid");
149});
150
151Deno.test("validate - local reference resolution", async () => {
152 const result = await validate([lexiconWithLocalRefs]);
153 if (result !== null)
154 throw new Error("Lexicon with local refs should be valid");
155});
156
157Deno.test("validateWithDetails - comprehensive reporting", async () => {
158 const result = await validateWithDetails([invalidLexicon]);
159 if (result.valid !== false) throw new Error("Should be invalid");
160 if (result.lexiconsWithErrors !== 1)
161 throw new Error("Should have 1 lexicon with errors");
162 if (!("com.example.broken" in result.errors))
163 throw new Error("Should contain broken lexicon");
164});
165
166Deno.test("validateWithDetails - valid lexicon", async () => {
167 const result = await validateWithDetails([validLexicon]);
168 if (result.valid !== true) throw new Error("Should be valid");
169 if (result.totalErrors !== 0) throw new Error("Should have no errors");
170});
171
172// ============================================================================
173// LexiconValidator Class Tests
174// ============================================================================
175
176Deno.test("LexiconValidator - valid record", async () => {
177 const validator = await LexiconValidator.create([validLexicon]);
178 const record = { text: "Hello!", author: "did:plc:test" };
179 validator.validateRecord("com.example.post", record);
180 validator.free();
181});
182
183Deno.test("LexiconValidator - invalid record", async () => {
184 const validator = await LexiconValidator.create([validLexicon]);
185 const record = { author: "did:plc:test" }; // missing required text
186
187 let threw = false;
188 try {
189 validator.validateRecord("com.example.post", record);
190 } catch (error) {
191 threw = true;
192 if (!(error instanceof ValidationError)) {
193 throw new Error("Should throw ValidationError");
194 }
195 }
196 if (!threw) throw new Error("Should have thrown for missing required field");
197 validator.free();
198});
199
200// ============================================================================
201// Standalone Function Tests
202// ============================================================================
203
204Deno.test("validateRecord standalone function", async () => {
205 const record = { text: "Test", author: "did:plc:test" };
206 await validateRecord([validLexicon], "com.example.post", record);
207});
208
209Deno.test("withValidator resource management", async () => {
210 const result = await withValidator([validLexicon], (validator) => {
211 const record = { text: "Test", author: "did:plc:test" };
212 validator.validateRecord("com.example.post", record);
213 return "success";
214 });
215 if (result !== "success") throw new Error("Should return callback result");
216});