atagen

malice

public
Star0
Watch0
d2a34d0f48616d4e9f590c082ba9f3f73091e402
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
132eeceeb362atagen2026-10-25 03:34 UTC50
test('avatar cache retries unsuccessful responses', async () => {
132eeceeb362atagen2026-10-25 03:34 UTC51
  const store = memoryCache();
132eeceeb362atagen2026-10-25 03:34 UTC52
  let loads = 0;
132eeceeb362atagen2026-10-25 03:34 UTC53
  const loader = createAvatarLoader(async () => store, async () => ++loads === 1 ? image('missing', 404) : image());
132eeceeb362atagen2026-10-25 03:34 UTC54
  assert.equal((await loader('/avatar', 'avatar')).status, 404);
132eeceeb362atagen2026-10-25 03:34 UTC55
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC56
  assert.equal(loads, 2);
132eeceeb362atagen2026-10-25 03:34 UTC57
});
132eeceeb362atagen2026-10-25 03:34 UTC58
132eeceeb362atagen2026-10-25 03:34 UTC59
test('avatar cache read failures still return the image', async () => {
132eeceeb362atagen2026-10-25 03:34 UTC60
  let loads = 0;
132eeceeb362atagen2026-10-25 03:34 UTC61
  const cache: AvatarCache = {
132eeceeb362atagen2026-10-25 03:34 UTC62
    async match() {
132eeceeb362atagen2026-10-25 03:34 UTC63
      throw new Error('read failed');
132eeceeb362atagen2026-10-25 03:34 UTC64
    },
132eeceeb362atagen2026-10-25 03:34 UTC65
    async put() {}
132eeceeb362atagen2026-10-25 03:34 UTC66
  };
132eeceeb362atagen2026-10-25 03:34 UTC67
  const loader = createAvatarLoader(async () => cache, async () => {
132eeceeb362atagen2026-10-25 03:34 UTC68
    loads += 1;
132eeceeb362atagen2026-10-25 03:34 UTC69
    return image();
132eeceeb362atagen2026-10-25 03:34 UTC70
  });
132eeceeb362atagen2026-10-25 03:34 UTC71
132eeceeb362atagen2026-10-25 03:34 UTC72
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC73
  assert.equal(loads, 1);
132eeceeb362atagen2026-10-25 03:34 UTC74
});
132eeceeb362atagen2026-10-25 03:34 UTC75
132eeceeb362atagen2026-10-25 03:34 UTC76
test('avatar cache write failures still return the image', async () => {
132eeceeb362atagen2026-10-25 03:34 UTC77
  let loads = 0;
132eeceeb362atagen2026-10-25 03:34 UTC78
  const cache: AvatarCache = {
132eeceeb362atagen2026-10-25 03:34 UTC79
    async match() {
132eeceeb362atagen2026-10-25 03:34 UTC80
      return undefined;
132eeceeb362atagen2026-10-25 03:34 UTC81
    },
132eeceeb362atagen2026-10-25 03:34 UTC82
    async put() {
132eeceeb362atagen2026-10-25 03:34 UTC83
      throw new Error('write failed');
132eeceeb362atagen2026-10-25 03:34 UTC84
    }
132eeceeb362atagen2026-10-25 03:34 UTC85
  };
132eeceeb362atagen2026-10-25 03:34 UTC86
  const loader = createAvatarLoader(async () => cache, async () => {
132eeceeb362atagen2026-10-25 03:34 UTC87
    loads += 1;
132eeceeb362atagen2026-10-25 03:34 UTC88
    return image();
132eeceeb362atagen2026-10-25 03:34 UTC89
  });
132eeceeb362atagen2026-10-25 03:34 UTC90
132eeceeb362atagen2026-10-25 03:34 UTC91
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
132eeceeb362atagen2026-10-25 03:34 UTC92
  assert.equal(loads, 1);
132eeceeb362atagen2026-10-25 03:34 UTC93
});