atagen

malice

public
Star0
Watch0
80e694c759dd0392548dd29b17590ae2decac8bc
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/test/avatar-cache.test.ts

132eeceeb362atagen2026-10-25 03:34 UTC1
import assert from 'node:assert/strict';
132eeceeb362atagen2026-10-25 03:34 UTC2
import { test } from 'node:test';
132eeceeb362atagen2026-10-25 03:34 UTC3
import { createAvatarLoader, type AvatarCache } from '../src/lib/avatar-cache.ts';
132eeceeb362atagen2026-10-25 03:34 UTC4
132eeceeb362atagen2026-10-25 03:34 UTC5
function memoryCache(): AvatarCache & { entries: Map<string, Response> } {
132eeceeb362atagen2026-10-25 03:34 UTC6
  const entries = new Map<string, Response>();
132eeceeb362atagen2026-10-25 03:34 UTC7
  return {
132eeceeb362atagen2026-10-25 03:34 UTC8
    entries,
132eeceeb362atagen2026-10-25 03:34 UTC9
    async match(key) {
132eeceeb362atagen2026-10-25 03:34 UTC10
      return entries.get(key)?.clone();
132eeceeb362atagen2026-10-25 03:34 UTC11
    },
132eeceeb362atagen2026-10-25 03:34 UTC12
    async put(key, response) {
132eeceeb362atagen2026-10-25 03:34 UTC13
      entries.set(key, response.clone());
132eeceeb362atagen2026-10-25 03:34 UTC14
    }
132eeceeb362atagen2026-10-25 03:34 UTC15
  };
132eeceeb362atagen2026-10-25 03:34 UTC16
}
132eeceeb362atagen2026-10-25 03:34 UTC17
132eeceeb362atagen2026-10-25 03:34 UTC18
function image(body = 'avatar', status = 200): Response {
132eeceeb362atagen2026-10-25 03:34 UTC19
  return new Response(body, { status, headers: { 'Content-Type': 'image/png' } });
132eeceeb362atagen2026-10-25 03:34 UTC20
}
132eeceeb362atagen2026-10-25 03:34 UTC21
132eeceeb362atagen2026-10-25 03:34 UTC22
test('avatar cache coalesces simultaneous misses and persists results', async () => {
132eeceeb362atagen2026-10-25 03:34 UTC23
  const store = memoryCache();
132eeceeb362atagen2026-10-25 03:34 UTC24
  let loads = 0;
132eeceeb362atagen2026-10-25 03:34 UTC25
  let release: ((response: Response) => void) | undefined;
132eeceeb362atagen2026-10-25 03:34 UTC26
  let signalLoadStarted: (() => void) | undefined;
132eeceeb362atagen2026-10-25 03:34 UTC27
  const loadStarted = new Promise<void>((resolve) => (signalLoadStarted = resolve));
132eeceeb362atagen2026-10-25 03:34 UTC28
  const loader = createAvatarLoader(async () => store, async () => {
132eeceeb362atagen2026-10-25 03:34 UTC29
    loads += 1;
132eeceeb362atagen2026-10-25 03:34 UTC30
    signalLoadStarted?.();
132eeceeb362atagen2026-10-25 03:34 UTC31
    return new Promise((resolve) => (release = resolve));
132eeceeb362atagen2026-10-25 03:34 UTC32
  });
132eeceeb362atagen2026-10-25 03:34 UTC33
132eeceeb362atagen2026-10-25 03:34 UTC34
  const first = loader('/api/media?mxc=avatar', 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC35
  const second = loader('/api/media?mxc=avatar', 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC36
  await loadStarted;
132eeceeb362atagen2026-10-25 03:34 UTC37
  assert.equal(loads, 1);
132eeceeb362atagen2026-10-25 03:34 UTC38
  release?.(image());
132eeceeb362atagen2026-10-25 03:34 UTC39
  assert.equal(await (await first).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC40
  assert.equal(await (await second).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC41
132eeceeb362atagen2026-10-25 03:34 UTC42
  const nextWorker = createAvatarLoader(async () => store, async () => {
132eeceeb362atagen2026-10-25 03:34 UTC43
    loads += 1;
132eeceeb362atagen2026-10-25 03:34 UTC44
    return image('unexpected');
132eeceeb362atagen2026-10-25 03:34 UTC45
  });
132eeceeb362atagen2026-10-25 03:34 UTC46
  assert.equal(await (await nextWorker('/api/media?mxc=avatar', 'avatar')).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC47
  assert.equal(loads, 1);
132eeceeb362atagen2026-10-25 03:34 UTC48
});
132eeceeb362atagen2026-10-25 03:34 UTC49
7643577c76bbatagen2026-10-25 07:37 UTC50
test('avatar cache keeps each size separate', async () => {
7643577c76bbatagen2026-10-25 07:37 UTC51
  const store = memoryCache();
7643577c76bbatagen2026-10-25 07:37 UTC52
  const loaded: string[] = [];
7643577c76bbatagen2026-10-25 07:37 UTC53
  const loader = createAvatarLoader(async () => store, async (query) => {
7643577c76bbatagen2026-10-25 07:37 UTC54
    loaded.push(query);
7643577c76bbatagen2026-10-25 07:37 UTC55
    return image(query);
7643577c76bbatagen2026-10-25 07:37 UTC56
  });
7643577c76bbatagen2026-10-25 07:37 UTC57
  const [small, large] = await Promise.all([
7643577c76bbatagen2026-10-25 07:37 UTC58
    loader('/api/media?mxc=avatar&size=32', 'mxc=avatar&size=32'),
7643577c76bbatagen2026-10-25 07:37 UTC59
    loader('/api/media?mxc=avatar&size=64', 'mxc=avatar&size=64')
7643577c76bbatagen2026-10-25 07:37 UTC60
  ]);
7643577c76bbatagen2026-10-25 07:37 UTC61
  assert.equal(await small.text(), 'mxc=avatar&size=32');
7643577c76bbatagen2026-10-25 07:37 UTC62
  assert.equal(await large.text(), 'mxc=avatar&size=64');
7643577c76bbatagen2026-10-25 07:37 UTC63
  assert.deepEqual(loaded.sort(), ['mxc=avatar&size=32', 'mxc=avatar&size=64']);
7643577c76bbatagen2026-10-25 07:37 UTC64
});
7643577c76bbatagen2026-10-25 07:37 UTC65
132eeceeb362atagen2026-10-25 03:34 UTC66
test('avatar cache retries unsuccessful responses', async () => {
132eeceeb362atagen2026-10-25 03:34 UTC67
  const store = memoryCache();
132eeceeb362atagen2026-10-25 03:34 UTC68
  let loads = 0;
132eeceeb362atagen2026-10-25 03:34 UTC69
  const loader = createAvatarLoader(async () => store, async () => ++loads === 1 ? image('missing', 404) : image());
132eeceeb362atagen2026-10-25 03:34 UTC70
  assert.equal((await loader('/avatar', 'avatar')).status, 404);
132eeceeb362atagen2026-10-25 03:34 UTC71
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC72
  assert.equal(loads, 2);
132eeceeb362atagen2026-10-25 03:34 UTC73
});
132eeceeb362atagen2026-10-25 03:34 UTC74
132eeceeb362atagen2026-10-25 03:34 UTC75
test('avatar cache read failures still return the image', async () => {
132eeceeb362atagen2026-10-25 03:34 UTC76
  let loads = 0;
132eeceeb362atagen2026-10-25 03:34 UTC77
  const cache: AvatarCache = {
132eeceeb362atagen2026-10-25 03:34 UTC78
    async match() {
132eeceeb362atagen2026-10-25 03:34 UTC79
      throw new Error('read failed');
132eeceeb362atagen2026-10-25 03:34 UTC80
    },
132eeceeb362atagen2026-10-25 03:34 UTC81
    async put() {}
132eeceeb362atagen2026-10-25 03:34 UTC82
  };
132eeceeb362atagen2026-10-25 03:34 UTC83
  const loader = createAvatarLoader(async () => cache, async () => {
132eeceeb362atagen2026-10-25 03:34 UTC84
    loads += 1;
132eeceeb362atagen2026-10-25 03:34 UTC85
    return image();
132eeceeb362atagen2026-10-25 03:34 UTC86
  });
132eeceeb362atagen2026-10-25 03:34 UTC87
132eeceeb362atagen2026-10-25 03:34 UTC88
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC89
  assert.equal(loads, 1);
132eeceeb362atagen2026-10-25 03:34 UTC90
});
132eeceeb362atagen2026-10-25 03:34 UTC91
132eeceeb362atagen2026-10-25 03:34 UTC92
test('avatar cache write failures still return the image', async () => {
132eeceeb362atagen2026-10-25 03:34 UTC93
  let loads = 0;
132eeceeb362atagen2026-10-25 03:34 UTC94
  const cache: AvatarCache = {
132eeceeb362atagen2026-10-25 03:34 UTC95
    async match() {
132eeceeb362atagen2026-10-25 03:34 UTC96
      return undefined;
132eeceeb362atagen2026-10-25 03:34 UTC97
    },
132eeceeb362atagen2026-10-25 03:34 UTC98
    async put() {
132eeceeb362atagen2026-10-25 03:34 UTC99
      throw new Error('write failed');
132eeceeb362atagen2026-10-25 03:34 UTC100
    }
132eeceeb362atagen2026-10-25 03:34 UTC101
  };
132eeceeb362atagen2026-10-25 03:34 UTC102
  const loader = createAvatarLoader(async () => cache, async () => {
132eeceeb362atagen2026-10-25 03:34 UTC103
    loads += 1;
132eeceeb362atagen2026-10-25 03:34 UTC104
    return image();
132eeceeb362atagen2026-10-25 03:34 UTC105
  });
132eeceeb362atagen2026-10-25 03:34 UTC106
132eeceeb362atagen2026-10-25 03:34 UTC107
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC108
  assert.equal(loads, 1);
132eeceeb362atagen2026-10-25 03:34 UTC109
});