Barazo AppView backend barazo.forum
at main 235 lines 7.3 kB view raw
1import { describe, it, expect } from 'vitest' 2import { searchQuerySchema } from '../../../src/validation/search.js' 3 4// --------------------------------------------------------------------------- 5// Tests 6// --------------------------------------------------------------------------- 7 8describe('search validation schemas', () => { 9 // ========================================================================= 10 // searchQuerySchema 11 // ========================================================================= 12 13 describe('searchQuerySchema', () => { 14 it('parses a valid query with all fields', () => { 15 const result = searchQuerySchema.safeParse({ 16 q: 'test query', 17 category: 'general', 18 author: 'did:plc:user123', 19 dateFrom: '2026-01-01T00:00:00Z', 20 dateTo: '2026-02-01T00:00:00Z', 21 type: 'topics', 22 limit: '10', 23 cursor: 'abc123base64', 24 }) 25 26 expect(result.success).toBe(true) 27 if (result.success) { 28 expect(result.data.q).toBe('test query') 29 expect(result.data.category).toBe('general') 30 expect(result.data.author).toBe('did:plc:user123') 31 expect(result.data.dateFrom).toBe('2026-01-01T00:00:00Z') 32 expect(result.data.dateTo).toBe('2026-02-01T00:00:00Z') 33 expect(result.data.type).toBe('topics') 34 expect(result.data.limit).toBe(10) 35 expect(result.data.cursor).toBe('abc123base64') 36 } 37 }) 38 39 it('parses a minimal valid query (only q)', () => { 40 const result = searchQuerySchema.safeParse({ q: 'hello' }) 41 42 expect(result.success).toBe(true) 43 if (result.success) { 44 expect(result.data.q).toBe('hello') 45 } 46 }) 47 48 it('fails when q is missing', () => { 49 const result = searchQuerySchema.safeParse({}) 50 expect(result.success).toBe(false) 51 }) 52 53 it('fails when q is empty string', () => { 54 const result = searchQuerySchema.safeParse({ q: '' }) 55 expect(result.success).toBe(false) 56 }) 57 58 it('fails when q exceeds 500 characters', () => { 59 const result = searchQuerySchema.safeParse({ q: 'a'.repeat(501) }) 60 expect(result.success).toBe(false) 61 }) 62 63 it('accepts q at exactly 500 characters', () => { 64 const result = searchQuerySchema.safeParse({ q: 'a'.repeat(500) }) 65 expect(result.success).toBe(true) 66 }) 67 68 it("defaults type to 'all' when not provided", () => { 69 const result = searchQuerySchema.safeParse({ q: 'test' }) 70 71 expect(result.success).toBe(true) 72 if (result.success) { 73 expect(result.data.type).toBe('all') 74 } 75 }) 76 77 it('defaults limit to 25 when not provided', () => { 78 const result = searchQuerySchema.safeParse({ q: 'test' }) 79 80 expect(result.success).toBe(true) 81 if (result.success) { 82 expect(result.data.limit).toBe(25) 83 } 84 }) 85 86 it("accepts type 'topics'", () => { 87 const result = searchQuerySchema.safeParse({ 88 q: 'test', 89 type: 'topics', 90 }) 91 expect(result.success).toBe(true) 92 if (result.success) { 93 expect(result.data.type).toBe('topics') 94 } 95 }) 96 97 it("accepts type 'replies'", () => { 98 const result = searchQuerySchema.safeParse({ 99 q: 'test', 100 type: 'replies', 101 }) 102 expect(result.success).toBe(true) 103 if (result.success) { 104 expect(result.data.type).toBe('replies') 105 } 106 }) 107 108 it("accepts type 'all'", () => { 109 const result = searchQuerySchema.safeParse({ q: 'test', type: 'all' }) 110 expect(result.success).toBe(true) 111 if (result.success) { 112 expect(result.data.type).toBe('all') 113 } 114 }) 115 116 it('fails for invalid type value', () => { 117 const result = searchQuerySchema.safeParse({ 118 q: 'test', 119 type: 'invalid', 120 }) 121 expect(result.success).toBe(false) 122 }) 123 124 it('fails when limit is below minimum (0)', () => { 125 const result = searchQuerySchema.safeParse({ q: 'test', limit: '0' }) 126 expect(result.success).toBe(false) 127 }) 128 129 it('fails when limit exceeds maximum (101)', () => { 130 const result = searchQuerySchema.safeParse({ q: 'test', limit: '101' }) 131 expect(result.success).toBe(false) 132 }) 133 134 it('accepts limit at boundary 1', () => { 135 const result = searchQuerySchema.safeParse({ q: 'test', limit: '1' }) 136 expect(result.success).toBe(true) 137 if (result.success) { 138 expect(result.data.limit).toBe(1) 139 } 140 }) 141 142 it('accepts limit at boundary 100', () => { 143 const result = searchQuerySchema.safeParse({ q: 'test', limit: '100' }) 144 expect(result.success).toBe(true) 145 if (result.success) { 146 expect(result.data.limit).toBe(100) 147 } 148 }) 149 150 it('transforms string limit to number', () => { 151 const result = searchQuerySchema.safeParse({ q: 'test', limit: '42' }) 152 expect(result.success).toBe(true) 153 if (result.success) { 154 expect(result.data.limit).toBe(42) 155 expect(typeof result.data.limit).toBe('number') 156 } 157 }) 158 159 it('fails for non-numeric limit', () => { 160 const result = searchQuerySchema.safeParse({ q: 'test', limit: 'abc' }) 161 expect(result.success).toBe(false) 162 }) 163 164 it('parses optional category filter', () => { 165 const result = searchQuerySchema.safeParse({ 166 q: 'test', 167 category: 'support', 168 }) 169 expect(result.success).toBe(true) 170 if (result.success) { 171 expect(result.data.category).toBe('support') 172 } 173 }) 174 175 it('parses optional author filter', () => { 176 const result = searchQuerySchema.safeParse({ 177 q: 'test', 178 author: 'did:plc:abc123', 179 }) 180 expect(result.success).toBe(true) 181 if (result.success) { 182 expect(result.data.author).toBe('did:plc:abc123') 183 } 184 }) 185 186 it('validates dateFrom as ISO datetime', () => { 187 const result = searchQuerySchema.safeParse({ 188 q: 'test', 189 dateFrom: '2026-01-15T10:30:00Z', 190 }) 191 expect(result.success).toBe(true) 192 if (result.success) { 193 expect(result.data.dateFrom).toBe('2026-01-15T10:30:00Z') 194 } 195 }) 196 197 it('validates dateTo as ISO datetime', () => { 198 const result = searchQuerySchema.safeParse({ 199 q: 'test', 200 dateTo: '2026-02-15T23:59:59Z', 201 }) 202 expect(result.success).toBe(true) 203 if (result.success) { 204 expect(result.data.dateTo).toBe('2026-02-15T23:59:59Z') 205 } 206 }) 207 208 it('fails for invalid dateFrom format', () => { 209 const result = searchQuerySchema.safeParse({ 210 q: 'test', 211 dateFrom: 'not-a-date', 212 }) 213 expect(result.success).toBe(false) 214 }) 215 216 it('fails for invalid dateTo format', () => { 217 const result = searchQuerySchema.safeParse({ 218 q: 'test', 219 dateTo: '2026/01/15', 220 }) 221 expect(result.success).toBe(false) 222 }) 223 224 it('parses optional cursor', () => { 225 const cursor = Buffer.from( 226 JSON.stringify({ rank: 0.5, uri: 'at://did:plc:test/forum.barazo.topic.post/abc' }) 227 ).toString('base64') 228 const result = searchQuerySchema.safeParse({ q: 'test', cursor }) 229 expect(result.success).toBe(true) 230 if (result.success) { 231 expect(result.data.cursor).toBe(cursor) 232 } 233 }) 234 }) 235})