Skip to content

Commit f8c7ca0

Browse files
committed
refactor: improve cookie hash comparison logic and tests
- Add clearer comments explaining hash-based comparison flow - Improve test coverage with more realistic scenarios - Add test for multiple assignments in single request - Update test names to be more descriptive
1 parent 2893f46 commit f8c7ca0

File tree

2 files changed

+72
-13
lines changed

2 files changed

+72
-13
lines changed

src/cookies.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export class Cookie<T> implements ElysiaCookie {
181181
// Simple equality check
182182
if (current === value) return
183183

184-
// For objects, do a deep equality check
184+
// For objects, use hash-based comparison for performance
185185
if (
186186
typeof current === 'object' &&
187187
current !== null &&
@@ -191,12 +191,15 @@ export class Cookie<T> implements ElysiaCookie {
191191
try {
192192
const newHash = hashObject(value)
193193

194+
// If hash differs from cached hash, value definitely changed
194195
if (this.valueHash !== undefined && this.valueHash !== newHash) {
195196
this.valueHash = newHash
196-
} else {
197+
}
198+
// First set (valueHash undefined) OR hashes match: do deep comparison
199+
else {
197200
if (JSON.stringify(current) === JSON.stringify(value)) {
198201
this.valueHash = newHash
199-
return
202+
return // Values are identical, skip update
200203
}
201204
this.valueHash = newHash
202205
}

test/cookie/unchanged.test.ts

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,26 +107,82 @@ describe('Cookie - Unchanged Values', () => {
107107
expect(response.headers.getAll('set-cookie').length).toBeGreaterThan(0)
108108
})
109109

110-
it('should handle repeated identical object values', async () => {
111-
const app = new Elysia().post('/', ({ cookie: { data } }) => {
112-
data.value = { id: 123, name: 'test' }
110+
it('should not send set-cookie header when setting same object value as incoming cookie', async () => {
111+
const app = new Elysia().post('/update', ({ cookie: { data } }) => {
112+
// Set to same value as incoming cookie
113113
data.value = { id: 123, name: 'test' }
114114
return 'ok'
115115
})
116116

117-
const res = await app.handle(new Request('http://localhost/', { method: 'POST' }))
118-
expect(res.headers.getAll('set-cookie').length).toBe(1)
117+
// First request: set the cookie
118+
const firstRes = await app.handle(
119+
new Request('http://localhost/update', { method: 'POST' })
120+
)
121+
const setCookie = firstRes.headers.get('set-cookie')
122+
expect(setCookie).toBeTruthy()
123+
124+
// Second request: send cookie back and set to same value
125+
const secondRes = await app.handle(
126+
new Request('http://localhost/update', {
127+
method: 'POST',
128+
headers: {
129+
cookie: setCookie!.split(';')[0]
130+
}
131+
})
132+
)
133+
134+
// Should not send Set-Cookie since value didn't change
135+
expect(secondRes.headers.getAll('set-cookie').length).toBe(0)
119136
})
120137

121-
it('should handle large object values efficiently', async () => {
122-
const large = { users: Array.from({ length: 100 }, (_, i) => ({ id: i, name: `User ${i}` })) }
123-
const app = new Elysia().post('/', ({ cookie: { data } }) => {
138+
it('should not send set-cookie header for large unchanged object values', async () => {
139+
const large = {
140+
users: Array.from({ length: 100 }, (_, i) => ({
141+
id: i,
142+
name: `User ${i}`
143+
}))
144+
}
145+
146+
const app = new Elysia().post('/update', ({ cookie: { data } }) => {
124147
data.value = large
125-
data.value = JSON.parse(JSON.stringify(large))
126148
return 'ok'
127149
})
128150

129-
const res = await app.handle(new Request('http://localhost/', { method: 'POST' }))
151+
// First request: set the cookie
152+
const firstRes = await app.handle(
153+
new Request('http://localhost/update', { method: 'POST' })
154+
)
155+
const setCookie = firstRes.headers.get('set-cookie')
156+
expect(setCookie).toBeTruthy()
157+
158+
// Second request: send cookie back and set to same value
159+
const secondRes = await app.handle(
160+
new Request('http://localhost/update', {
161+
method: 'POST',
162+
headers: {
163+
cookie: setCookie!.split(';')[0]
164+
}
165+
})
166+
)
167+
168+
// Should not send Set-Cookie since value didn't change
169+
expect(secondRes.headers.getAll('set-cookie').length).toBe(0)
170+
})
171+
172+
it('should optimize multiple assignments of same object in single request', async () => {
173+
const app = new Elysia().post('/multi', ({ cookie: { data } }) => {
174+
// Multiple assignments of the same value
175+
data.value = { id: 123, name: 'test' }
176+
data.value = { id: 123, name: 'test' }
177+
data.value = { id: 123, name: 'test' }
178+
return 'ok'
179+
})
180+
181+
const res = await app.handle(
182+
new Request('http://localhost/multi', { method: 'POST' })
183+
)
184+
185+
// Should only produce one Set-Cookie header
130186
expect(res.headers.getAll('set-cookie').length).toBe(1)
131187
})
132188
})

0 commit comments

Comments
 (0)