atagen

malice

public
Star0
Watch0
cf020696433e54fc5fb60de3b67218baef8c4748
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/test/avatar-cache.test.tsTypescript93 lines2.9 KB
1
import assert from 'node:assert/strict';
2
import { test } from 'node:test';
3
import { createAvatarLoader, type AvatarCache } from '../src/lib/avatar-cache.ts';
4
5
function memoryCache(): AvatarCache & { entries: Map<string, Response> } {
6
  const entries = new Map<string, Response>();
7
  return {
8
    entries,
9
    async match(key) {
10
      return entries.get(key)?.clone();
11
    },
12
    async put(key, response) {
13
      entries.set(key, response.clone());
14
    }
15
  };
16
}
17
18
function image(body = 'avatar', status = 200): Response {
19
  return new Response(body, { status, headers: { 'Content-Type': 'image/png' } });
20
}
21
22
test('avatar cache coalesces simultaneous misses and persists results', async () => {
23
  const store = memoryCache();
24
  let loads = 0;
25
  let release: ((response: Response) => void) | undefined;
26
  let signalLoadStarted: (() => void) | undefined;
27
  const loadStarted = new Promise<void>((resolve) => (signalLoadStarted = resolve));
28
  const loader = createAvatarLoader(async () => store, async () => {
29
    loads += 1;
30
    signalLoadStarted?.();
31
    return new Promise((resolve) => (release = resolve));
32
  });
33
34
  const first = loader('/api/media?mxc=avatar', 'avatar');
35
  const second = loader('/api/media?mxc=avatar', 'avatar');
36
  await loadStarted;
37
  assert.equal(loads, 1);
38
  release?.(image());
39
  assert.equal(await (await first).text(), 'avatar');
40
  assert.equal(await (await second).text(), 'avatar');
41
42
  const nextWorker = createAvatarLoader(async () => store, async () => {
43
    loads += 1;
44
    return image('unexpected');
45
  });
46
  assert.equal(await (await nextWorker('/api/media?mxc=avatar', 'avatar')).text(), 'avatar');
47
  assert.equal(loads, 1);
48
});
49
50
test('avatar cache retries unsuccessful responses', async () => {
51
  const store = memoryCache();
52
  let loads = 0;
53
  const loader = createAvatarLoader(async () => store, async () => ++loads === 1 ? image('missing', 404) : image());
54
  assert.equal((await loader('/avatar', 'avatar')).status, 404);
55
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
56
  assert.equal(loads, 2);
57
});
58
59
test('avatar cache read failures still return the image', async () => {
60
  let loads = 0;
61
  const cache: AvatarCache = {
62
    async match() {
63
      throw new Error('read failed');
64
    },
65
    async put() {}
66
  };
67
  const loader = createAvatarLoader(async () => cache, async () => {
68
    loads += 1;
69
    return image();
70
  });
71
72
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
73
  assert.equal(loads, 1);
74
});
75
76
test('avatar cache write failures still return the image', async () => {
77
  let loads = 0;
78
  const cache: AvatarCache = {
79
    async match() {
80
      return undefined;
81
    },
82
    async put() {
83
      throw new Error('write failed');
84
    }
85
  };
86
  const loader = createAvatarLoader(async () => cache, async () => {
87
    loads += 1;
88
    return image();
89
  });
90
91
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
92
  assert.equal(loads, 1);
93
});