Barazo AppView backend
barazo.forum
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})