Barazo AppView backend barazo.forum
at main 181 lines 5.9 kB view raw
1import { describe, it, expect } from 'vitest' 2import { notificationQuerySchema, markReadSchema } from '../../../src/validation/notifications.js' 3 4// --------------------------------------------------------------------------- 5// Tests 6// --------------------------------------------------------------------------- 7 8describe('notification validation schemas', () => { 9 // ========================================================================= 10 // notificationQuerySchema 11 // ========================================================================= 12 13 describe('notificationQuerySchema', () => { 14 it('parses a valid query with all fields', () => { 15 const result = notificationQuerySchema.safeParse({ 16 limit: '10', 17 cursor: 'abc123base64', 18 unreadOnly: 'true', 19 }) 20 21 expect(result.success).toBe(true) 22 if (result.success) { 23 expect(result.data.limit).toBe(10) 24 expect(result.data.cursor).toBe('abc123base64') 25 expect(result.data.unreadOnly).toBe(true) 26 } 27 }) 28 29 it('parses a minimal valid query (no fields)', () => { 30 const result = notificationQuerySchema.safeParse({}) 31 32 expect(result.success).toBe(true) 33 if (result.success) { 34 expect(result.data.limit).toBe(25) 35 expect(result.data.cursor).toBeUndefined() 36 expect(result.data.unreadOnly).toBeUndefined() 37 } 38 }) 39 40 it('defaults limit to 25 when not provided', () => { 41 const result = notificationQuerySchema.safeParse({}) 42 43 expect(result.success).toBe(true) 44 if (result.success) { 45 expect(result.data.limit).toBe(25) 46 } 47 }) 48 49 it('transforms string limit to number', () => { 50 const result = notificationQuerySchema.safeParse({ limit: '42' }) 51 expect(result.success).toBe(true) 52 if (result.success) { 53 expect(result.data.limit).toBe(42) 54 expect(typeof result.data.limit).toBe('number') 55 } 56 }) 57 58 it('accepts limit at boundary 1', () => { 59 const result = notificationQuerySchema.safeParse({ limit: '1' }) 60 expect(result.success).toBe(true) 61 if (result.success) { 62 expect(result.data.limit).toBe(1) 63 } 64 }) 65 66 it('accepts limit at boundary 100', () => { 67 const result = notificationQuerySchema.safeParse({ limit: '100' }) 68 expect(result.success).toBe(true) 69 if (result.success) { 70 expect(result.data.limit).toBe(100) 71 } 72 }) 73 74 it('fails when limit is below minimum (0)', () => { 75 const result = notificationQuerySchema.safeParse({ limit: '0' }) 76 expect(result.success).toBe(false) 77 }) 78 79 it('fails when limit exceeds maximum (101)', () => { 80 const result = notificationQuerySchema.safeParse({ limit: '101' }) 81 expect(result.success).toBe(false) 82 }) 83 84 it('fails for non-numeric limit', () => { 85 const result = notificationQuerySchema.safeParse({ limit: 'abc' }) 86 expect(result.success).toBe(false) 87 }) 88 89 it("transforms unreadOnly 'true' to boolean true", () => { 90 const result = notificationQuerySchema.safeParse({ 91 unreadOnly: 'true', 92 }) 93 expect(result.success).toBe(true) 94 if (result.success) { 95 expect(result.data.unreadOnly).toBe(true) 96 } 97 }) 98 99 it("transforms unreadOnly 'false' to boolean false", () => { 100 const result = notificationQuerySchema.safeParse({ 101 unreadOnly: 'false', 102 }) 103 expect(result.success).toBe(true) 104 if (result.success) { 105 expect(result.data.unreadOnly).toBe(false) 106 } 107 }) 108 109 it('parses optional cursor', () => { 110 const cursor = Buffer.from( 111 JSON.stringify({ createdAt: '2026-02-14T12:00:00Z', id: 42 }) 112 ).toString('base64') 113 const result = notificationQuerySchema.safeParse({ cursor }) 114 expect(result.success).toBe(true) 115 if (result.success) { 116 expect(result.data.cursor).toBe(cursor) 117 } 118 }) 119 }) 120 121 // ========================================================================= 122 // markReadSchema 123 // ========================================================================= 124 125 describe('markReadSchema', () => { 126 it('parses with notificationId', () => { 127 const result = markReadSchema.safeParse({ notificationId: 42 }) 128 expect(result.success).toBe(true) 129 if (result.success) { 130 expect(result.data.notificationId).toBe(42) 131 expect(result.data.all).toBeUndefined() 132 } 133 }) 134 135 it('parses with all: true', () => { 136 const result = markReadSchema.safeParse({ all: true }) 137 expect(result.success).toBe(true) 138 if (result.success) { 139 expect(result.data.all).toBe(true) 140 expect(result.data.notificationId).toBeUndefined() 141 } 142 }) 143 144 it('parses with both notificationId and all', () => { 145 const result = markReadSchema.safeParse({ 146 notificationId: 1, 147 all: true, 148 }) 149 expect(result.success).toBe(true) 150 }) 151 152 it('parses empty object (both optional)', () => { 153 const result = markReadSchema.safeParse({}) 154 expect(result.success).toBe(true) 155 if (result.success) { 156 expect(result.data.notificationId).toBeUndefined() 157 expect(result.data.all).toBeUndefined() 158 } 159 }) 160 161 it('fails for negative notificationId', () => { 162 const result = markReadSchema.safeParse({ notificationId: -1 }) 163 expect(result.success).toBe(false) 164 }) 165 166 it('fails for zero notificationId', () => { 167 const result = markReadSchema.safeParse({ notificationId: 0 }) 168 expect(result.success).toBe(false) 169 }) 170 171 it('fails for non-integer notificationId', () => { 172 const result = markReadSchema.safeParse({ notificationId: 1.5 }) 173 expect(result.success).toBe(false) 174 }) 175 176 it('fails for non-boolean all', () => { 177 const result = markReadSchema.safeParse({ all: 'yes' }) 178 expect(result.success).toBe(false) 179 }) 180 }) 181})