import { createHash } from 'node:crypto'; import { describe, expect, it } from 'vitest'; import { hashHotlinkAccessToken } from '@/utils/crypto/hashHotlinkAccessToken'; const hashAccessTokenLikeBackend = (accessToken: string): string => createHash('sha256').update(accessToken, 'utf8').digest('hex'); const hotlinkAccessTokenHashVectors = [ { accessToken: 'test-hotlink-access-token_base64url', accessTokenHash: '8defaf078afdd0ddabb77c73af4aa76ac78be24b6a6deffba2c6cfde43e20187' }, { accessToken: 'ZWc6ttO-r2qssA_9gdWe6u14X2W0_j7xhRBSVQvfMKI', accessTokenHash: 'cdb772980ea3e958cf74b62b70a2f310eafbb9bf3d2302356b602b4830e89bc6' } ] as const; describe('hashHotlinkAccessToken', () => { it.each(hotlinkAccessTokenHashVectors)( 'matches fixed SHA-256 hex for $accessToken', async ({ accessToken, accessTokenHash }) => { await expect(hashHotlinkAccessToken(accessToken)).resolves.toBe(accessTokenHash); } ); it('matches backend hashHotlinkAccessToken (Node crypto) for arbitrary tokens', async () => { const accessToken = 'another_base64url-style_secret-ABC123'; await expect(hashHotlinkAccessToken(accessToken)).resolves.toBe(hashAccessTokenLikeBackend(accessToken)); }); it('returns 64 lowercase hex characters (SHA-256 digest)', async () => { const hash = await hashHotlinkAccessToken(hotlinkAccessTokenHashVectors[0].accessToken); expect(hash).toMatch(/^[a-f0-9]{64}$/); }); it('changes when the access token string changes', async () => { const [first, second] = hotlinkAccessTokenHashVectors; expect(first.accessTokenHash).not.toBe(second.accessTokenHash); }); it('hashes the UTF-8 string, not base64url-decoded bytes', async () => { const accessToken = 'AQ'; await expect(hashHotlinkAccessToken(accessToken)).resolves.toBe(hashAccessTokenLikeBackend(accessToken)); }); });