Skip to content
markpaper

src/rest/authToken.test.ts

v0.2.1 · 1.7 KB

Download file
import { describe, expect, it } from 'vitest';
import { AUTH_TOKEN_LIFETIME_MS, authHeader, createAuthTokenCache, DEFAULT_AUTH_TOKEN_TTL_MS } from './authToken.js';

describe('createAuthTokenCache', () => {
  it('caches for the TTL (half the token lifetime by default) and refreshes after', async () => {
    expect(DEFAULT_AUTH_TOKEN_TTL_MS * 2).toBe(AUTH_TOKEN_LIFETIME_MS);
    let t = 0;
    let n = 0;
    const cache = createAuthTokenCache({ getToken: async () => `tok-${++n}`, now: () => t });
    expect(await cache.get()).toBe('tok-1');
    t = DEFAULT_AUTH_TOKEN_TTL_MS - 1;
    expect(await cache.get()).toBe('tok-1');
    t = DEFAULT_AUTH_TOKEN_TTL_MS;
    expect(await cache.get()).toBe('tok-2');
    expect(cache.peek()).toEqual({ token: 'tok-2', expiresAt: t + DEFAULT_AUTH_TOKEN_TTL_MS });
  });

  it('invalidate forces a new token (401/403 path)', async () => {
    let n = 0;
    const cache = createAuthTokenCache({ getToken: async () => `tok-${++n}` });
    await cache.get();
    cache.invalidate();
    expect(cache.peek()).toBeNull();
    expect(await cache.get()).toBe('tok-2');
  });

  it('shares one in-flight fetch and rejects empty tokens', async () => {
    let n = 0;
    const cache = createAuthTokenCache({
      getToken: async () => {
        n++;
        await new Promise((r) => setTimeout(r, 5));
        return 'tok';
      },
    });
    await Promise.all([cache.get(), cache.get(), cache.get()]);
    expect(n).toBe(1);

    const empty = createAuthTokenCache({ getToken: async () => '' });
    await expect(empty.get()).rejects.toThrow(/empty token/);
    expect(empty.peek()).toBeNull();
  });

  it('authHeader has no Bearer prefix', () => {
    expect(authHeader('abc')).toEqual({ authorization: 'abc' });
  });
});
All files