A plain JavaScript validator for AT Protocol lexicon schemas
at main 298 lines 6.2 kB view raw
1// Test inputs for ref data validation 2 3// Lexicon with a string definition 4const stringRefLexicon = { 5 lexicon: 1, 6 id: 'app.test.stringref', 7 defs: { 8 main: { 9 type: 'record', 10 key: 'tid', 11 record: { 12 type: 'object', 13 properties: { 14 content: { type: 'ref', ref: '#post' }, 15 }, 16 }, 17 }, 18 post: { 19 type: 'string', 20 maxLength: 280, 21 }, 22 }, 23}; 24 25// Lexicon with an object definition 26const objectRefLexicon = { 27 lexicon: 1, 28 id: 'app.test.objectref', 29 defs: { 30 main: { 31 type: 'record', 32 key: 'tid', 33 record: { 34 type: 'object', 35 properties: { 36 user: { type: 'ref', ref: '#userDef' }, 37 }, 38 }, 39 }, 40 userDef: { 41 type: 'object', 42 required: ['name'], 43 properties: { 44 name: { type: 'string' }, 45 age: { type: 'integer' }, 46 }, 47 }, 48 }, 49}; 50 51// Lexicon with nested reference chain: refA -> refB -> actualString 52const nestedRefLexicon = { 53 lexicon: 1, 54 id: 'app.test.nested', 55 defs: { 56 main: { 57 type: 'record', 58 key: 'tid', 59 record: { 60 type: 'object', 61 properties: { 62 data: { type: 'ref', ref: '#refA' }, 63 }, 64 }, 65 }, 66 refA: { 67 type: 'ref', 68 ref: '#refB', 69 }, 70 refB: { 71 type: 'ref', 72 ref: '#actualString', 73 }, 74 actualString: { 75 type: 'string', 76 }, 77 }, 78}; 79 80// Lexicon with circular reference: refA -> refB -> refA 81const circularRefLexicon = { 82 lexicon: 1, 83 id: 'app.test.circular', 84 defs: { 85 main: { 86 type: 'record', 87 key: 'tid', 88 record: { 89 type: 'object', 90 properties: { 91 data: { type: 'ref', ref: '#refA' }, 92 }, 93 }, 94 }, 95 refA: { 96 type: 'ref', 97 ref: '#refB', 98 }, 99 refB: { 100 type: 'ref', 101 ref: '#refA', 102 }, 103 }, 104}; 105 106// Cross-lexicon reference 107const crossRefLexicon1 = { 108 lexicon: 1, 109 id: 'app.test.schema', 110 defs: { 111 main: { 112 type: 'record', 113 key: 'tid', 114 record: { 115 type: 'object', 116 properties: { 117 user: { type: 'ref', ref: 'app.test.types#user' }, 118 }, 119 }, 120 }, 121 }, 122}; 123 124const crossRefLexicon2 = { 125 lexicon: 1, 126 id: 'app.test.types', 127 defs: { 128 user: { 129 type: 'object', 130 required: ['id'], 131 properties: { 132 id: { type: 'string' }, 133 }, 134 }, 135 }, 136}; 137 138// Lexicon with nonexistent ref 139const badRefLexicon = { 140 lexicon: 1, 141 id: 'app.test.badref', 142 defs: { 143 main: { 144 type: 'record', 145 key: 'tid', 146 record: { 147 type: 'object', 148 properties: { 149 data: { type: 'ref', ref: '#nonexistent' }, 150 }, 151 }, 152 }, 153 }, 154}; 155 156// Cross-lexicon reference with nested local refs 157// Tests: when A refs B#foo, and B#foo refs #bar, it should resolve to B#bar (not A#bar) 158// This mirrors real-world case: app.bsky.actor.profile -> com.atproto.label.defs#selfLabels -> #selfLabel 159const crossRefNestedLexicon1 = { 160 lexicon: 1, 161 id: 'app.test.profile', 162 defs: { 163 main: { 164 type: 'record', 165 key: 'tid', 166 record: { 167 type: 'object', 168 properties: { 169 labels: { type: 'ref', ref: 'app.test.labels#selfLabels' }, 170 }, 171 }, 172 }, 173 // This should NOT be used - the ref should resolve to app.test.labels#selfLabel 174 selfLabel: { 175 type: 'object', 176 required: ['wrongField'], 177 properties: { 178 wrongField: { type: 'string' }, 179 }, 180 }, 181 }, 182}; 183 184const crossRefNestedLexicon2 = { 185 lexicon: 1, 186 id: 'app.test.labels', 187 defs: { 188 selfLabels: { 189 type: 'object', 190 properties: { 191 values: { 192 type: 'array', 193 items: { type: 'ref', ref: '#selfLabel' }, 194 }, 195 }, 196 }, 197 selfLabel: { 198 type: 'object', 199 required: ['val'], 200 properties: { 201 val: { type: 'string' }, 202 }, 203 }, 204 }, 205}; 206 207export const refDataInputs = [ 208 { 209 name: 'ref-data-valid-to-string', 210 lexicons: [stringRefLexicon], 211 collection: 'app.test.stringref', 212 record: { 213 content: 'Hello, world!', 214 }, 215 }, 216 { 217 name: 'ref-data-invalid-string-too-long', 218 lexicons: [stringRefLexicon], 219 collection: 'app.test.stringref', 220 record: { 221 content: 'a'.repeat(281), 222 }, 223 }, 224 { 225 name: 'ref-data-valid-to-object', 226 lexicons: [objectRefLexicon], 227 collection: 'app.test.objectref', 228 record: { 229 user: { name: 'Alice', age: 30 }, 230 }, 231 }, 232 { 233 name: 'ref-data-invalid-object-missing-required', 234 lexicons: [objectRefLexicon], 235 collection: 'app.test.objectref', 236 record: { 237 user: { age: 30 }, 238 }, 239 }, 240 { 241 name: 'ref-data-valid-nested-chain', 242 lexicons: [nestedRefLexicon], 243 collection: 'app.test.nested', 244 record: { 245 data: 'Hello!', 246 }, 247 }, 248 { 249 name: 'ref-data-invalid-circular-reference', 250 lexicons: [circularRefLexicon], 251 collection: 'app.test.circular', 252 record: { 253 data: 'test', 254 }, 255 }, 256 { 257 name: 'ref-data-valid-cross-lexicon', 258 lexicons: [crossRefLexicon1, crossRefLexicon2], 259 collection: 'app.test.schema', 260 record: { 261 user: { id: 'user123' }, 262 }, 263 }, 264 { 265 name: 'ref-data-invalid-cross-lexicon-missing-required', 266 lexicons: [crossRefLexicon1, crossRefLexicon2], 267 collection: 'app.test.schema', 268 record: { 269 user: { name: 'Alice' }, 270 }, 271 }, 272 { 273 name: 'ref-data-invalid-not-found', 274 lexicons: [badRefLexicon], 275 collection: 'app.test.badref', 276 record: { 277 data: 'test', 278 }, 279 }, 280 // Cross-lexicon ref with nested local ref - should resolve to target lexicon's def 281 { 282 name: 'ref-data-valid-cross-lexicon-nested-local-ref', 283 lexicons: [crossRefNestedLexicon1, crossRefNestedLexicon2], 284 collection: 'app.test.profile', 285 record: { 286 labels: { values: [{ val: 'self-label-value' }] }, 287 }, 288 }, 289 // This should fail because it uses the wrong lexicon's selfLabel definition 290 { 291 name: 'ref-data-invalid-cross-lexicon-wrong-context', 292 lexicons: [crossRefNestedLexicon1, crossRefNestedLexicon2], 293 collection: 'app.test.profile', 294 record: { 295 labels: { values: [{ wrongField: 'this-uses-wrong-lexicon' }] }, 296 }, 297 }, 298];