|
5 | 5 | import { createMockRedis } from '@sim/testing/mocks/redis.mock' |
6 | 6 | import { redisConfigMockFns } from '@sim/testing/mocks/redis-config.mock' |
7 | 7 | import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 8 | +import { MAX_ICON_BYTES } from '@/lib/favicon/probe' |
8 | 9 | import { resolveFavicon } from '@/lib/favicon/resolve.server' |
9 | 10 |
|
10 | 11 | vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock) |
@@ -86,4 +87,47 @@ describe('resolveFavicon', () => { |
86 | 87 | expect((await resolveFavicon('acme.example.com', admit)).kind).toBe('hit') |
87 | 88 | expect(admit).not.toHaveBeenCalled() |
88 | 89 | }) |
| 90 | + |
| 91 | + it('looks up an icon cached under an earlier, larger cap again instead of serving it', async () => { |
| 92 | + answers = { 'acme.example.com': 'icon' } |
| 93 | + const oversized = Buffer.concat([Buffer.from('h'), PNG, Buffer.alloc(MAX_ICON_BYTES)]) |
| 94 | + stored.set('favicon:v3:acme.example.com', { value: oversized, ttl: 30 * DAY }) |
| 95 | + const result = await resolveFavicon('acme.example.com') |
| 96 | + expect(result.kind === 'hit' && result.bytes.length).toBe(PNG.length) |
| 97 | + expect(inputValidationMockFns.mockSecureFetchWithValidation).toHaveBeenCalled() |
| 98 | + }) |
| 99 | + |
| 100 | + it("never lets one caller's refused budget answer another caller's lookup", async () => { |
| 101 | + answers = { 'acme.example.com': 'icon' } |
| 102 | + const [refused, admitted] = await Promise.all([ |
| 103 | + resolveFavicon('acme.example.com', async () => false), |
| 104 | + resolveFavicon('acme.example.com', async () => true), |
| 105 | + ]) |
| 106 | + expect(refused.kind).toBe('unavailable') |
| 107 | + expect(admitted.kind).toBe('hit') |
| 108 | + }) |
| 109 | + |
| 110 | + it('does not charge a caller who joins a lookup already asking the providers', async () => { |
| 111 | + answers = { 'acme.example.com': 'icon' } |
| 112 | + const fetchProvider = inputValidationMockFns.mockSecureFetchWithValidation |
| 113 | + const answerProvider = fetchProvider.getMockImplementation() |
| 114 | + let release = () => {} |
| 115 | + const released = new Promise<void>((resolve) => { |
| 116 | + release = resolve |
| 117 | + }) |
| 118 | + fetchProvider.mockImplementation( |
| 119 | + async (...args: Parameters<NonNullable<typeof answerProvider>>) => { |
| 120 | + await released |
| 121 | + return answerProvider?.(...args) |
| 122 | + } |
| 123 | + ) |
| 124 | + const first = resolveFavicon('acme.example.com') |
| 125 | + await vi.waitFor(() => expect(fetchProvider).toHaveBeenCalled()) |
| 126 | + const joinerAdmit = vi.fn(async () => true) |
| 127 | + const joined = resolveFavicon('acme.example.com', joinerAdmit) |
| 128 | + release() |
| 129 | + expect((await joined).kind).toBe('hit') |
| 130 | + expect((await first).kind).toBe('hit') |
| 131 | + expect(joinerAdmit).not.toHaveBeenCalled() |
| 132 | + }) |
89 | 133 | }) |
0 commit comments