import { describe, it, expect } from 'vitest'; import type { FastifyRequest, FastifyReply } from 'fastify'; import type { NodeOAuthClient } from '@atproto/oauth-client-node'; import { TokenInvalidError, TokenRefreshError, TokenRevokedError, } from '@atproto/oauth-client-node'; import type { Database } from '../../src/db/index.js'; import { createAuthMiddleware } from '../../src/middleware/auth.js'; import { isPermanentSessionError } from '../../src/oauth/errors.js'; describe('Auth middleware', () => { it('returns 401 when no session cookie', async () => { const middleware = createAuthMiddleware( {} as unknown as NodeOAuthClient, {} as unknown as Database, ); const request = { cookies: {} } as unknown as FastifyRequest; const reply = { status: (code: number) => ({ send: (body: Record) => ({ statusCode: code, body }), }), } as unknown as FastifyReply; const result = await middleware(request, reply); expect((result as { statusCode: number }).statusCode).toBe(401); }); it('returns 503 when oauthClient is null', async () => { const middleware = createAuthMiddleware(null, {} as unknown as Database); const request = { cookies: { session: 'some-session-id' } } as unknown as FastifyRequest; const reply = { status: (code: number) => ({ send: (body: Record) => ({ statusCode: code, body }), }), } as unknown as FastifyReply; const result = await middleware(request, reply); expect((result as { statusCode: number }).statusCode).toBe(503); }); }); describe('isPermanentSessionError', () => { const testDid = 'did:plc:test123'; it('returns true for TokenInvalidError', () => { expect(isPermanentSessionError(new TokenInvalidError(testDid))).toBe(true); expect(isPermanentSessionError(new TokenInvalidError(testDid, 'invalid_grant'))).toBe(true); }); it('returns true for TokenRevokedError', () => { expect(isPermanentSessionError(new TokenRevokedError(testDid))).toBe(true); }); it('returns true for TokenRefreshError caused by a permanent error', () => { const cause = new TokenInvalidError(testDid); const err = new TokenRefreshError(testDid, 'Refresh failed', { cause }); expect(isPermanentSessionError(err)).toBe(true); }); it('returns true for TokenRefreshError caused by a revoked token', () => { const cause = new TokenRevokedError(testDid); const err = new TokenRefreshError(testDid, 'Refresh failed', { cause }); expect(isPermanentSessionError(err)).toBe(true); }); it('returns false for TokenRefreshError with a transient cause', () => { const cause = new Error('fetch failed'); const err = new TokenRefreshError(testDid, 'Refresh failed', { cause }); expect(isPermanentSessionError(err)).toBe(false); }); it('returns false for TokenRefreshError with no cause', () => { const err = new TokenRefreshError(testDid, 'Refresh failed'); expect(isPermanentSessionError(err)).toBe(false); }); it('returns false for network errors (transient)', () => { expect(isPermanentSessionError(new Error('fetch failed'))).toBe(false); expect(isPermanentSessionError(new Error('connect ECONNREFUSED'))).toBe(false); expect(isPermanentSessionError(new Error('ETIMEDOUT'))).toBe(false); }); it('returns false for rate limiting errors (transient)', () => { const err = new Error('Failed to resolve identity: did:plc:abc'); err.cause = new Error('Too Many Requests'); expect(isPermanentSessionError(err)).toBe(false); }); it('returns false for unknown errors (safe default: transient)', () => { expect(isPermanentSessionError(new Error('Something unexpected happened'))).toBe(false); expect(isPermanentSessionError(new Error('New error type from library v2'))).toBe(false); }); it('returns false for non-Error values', () => { expect(isPermanentSessionError('string error')).toBe(false); expect(isPermanentSessionError(null)).toBe(false); }); });