import {describe, it, expect, vi, beforeEach} from 'vitest';
import {
auditOauthStarted,
auditOauthSuccess,
auditOauthFailure,
auditTokenExchangeSuccess,
auditTokenExchangeFailure,
auditTokenRevoked,
auditAccessDenied,
maskToken,
maskEmail,
} from './audit.js';
import {logger} from './logger.js';
vi.mock('./logger.js', () => ({
logger: {
info: vi.fn(),
warn: vi.fn(),
},
}));
describe('audit', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('auditOauthStarted', () => {
it('logs with info level', () => {
auditOauthStarted({clientId: 'client-123', redirectUri: 'https://example.com'});
expect(logger.info).toHaveBeenCalledWith({
audit: true,
event: 'oauth_started',
clientId: 'client-123',
redirectUri: 'https://example.com',
});
});
});
describe('auditOauthSuccess', () => {
it('logs with info level', () => {
auditOauthSuccess({userId: 'user@example.com'});
expect(logger.info).toHaveBeenCalledWith({
audit: true,
event: 'oauth_success',
userId: 'user@example.com',
});
});
});
describe('auditOauthFailure', () => {
it('logs with warn level', () => {
auditOauthFailure({reason: 'invalid_state'});
expect(logger.warn).toHaveBeenCalledWith({
audit: true,
event: 'oauth_failure',
reason: 'invalid_state',
});
});
});
describe('auditTokenExchangeSuccess', () => {
it('logs with info level', () => {
auditTokenExchangeSuccess({clientId: 'client-456'});
expect(logger.info).toHaveBeenCalledWith({
audit: true,
event: 'token_exchange_success',
clientId: 'client-456',
});
});
});
describe('auditTokenExchangeFailure', () => {
it('logs with warn level', () => {
auditTokenExchangeFailure({reason: 'invalid_code'});
expect(logger.warn).toHaveBeenCalledWith({
audit: true,
event: 'token_exchange_failure',
reason: 'invalid_code',
});
});
it('includes clientId when provided', () => {
auditTokenExchangeFailure({reason: 'client_mismatch', clientId: 'client-789'});
expect(logger.warn).toHaveBeenCalledWith({
audit: true,
event: 'token_exchange_failure',
reason: 'client_mismatch',
clientId: 'client-789',
});
});
});
describe('auditTokenRevoked', () => {
it('logs with info level', () => {
auditTokenRevoked({userId: 'user@example.com'});
expect(logger.info).toHaveBeenCalledWith({
audit: true,
event: 'token_revoked',
userId: 'user@example.com',
});
});
});
describe('auditAccessDenied', () => {
it('logs with warn level', () => {
auditAccessDenied({reason: 'invalid_token'});
expect(logger.warn).toHaveBeenCalledWith({
audit: true,
event: 'access_denied',
reason: 'invalid_token',
});
});
it('includes optional fields when provided', () => {
auditAccessDenied({
reason: 'token_refresh_failed',
userId: 'user@example.com',
error: 'Token expired',
});
expect(logger.warn).toHaveBeenCalledWith({
audit: true,
event: 'access_denied',
reason: 'token_refresh_failed',
userId: 'user@example.com',
error: 'Token expired',
});
});
});
describe('maskToken', () => {
it('masks short tokens (<=8 chars) completely', () => {
expect(maskToken('short')).toBe('***');
expect(maskToken('12345678')).toBe('***');
});
it('masks tokens longer than 8 chars with first/last 4', () => {
expect(maskToken('123456789')).toBe('1234...6789');
expect(maskToken('abcdefghijklmnop')).toBe('abcd...mnop');
});
it('handles exactly 9 character token', () => {
expect(maskToken('123456789')).toBe('1234...6789');
});
it('handles empty string', () => {
expect(maskToken('')).toBe('***');
});
});
describe('maskEmail', () => {
it('masks normal email addresses', () => {
expect(maskEmail('user@example.com')).toBe('u***@example.com');
});
it('masks email with single char before @', () => {
expect(maskEmail('a@example.com')).toBe('***@example.com');
});
it('masks email starting with @', () => {
expect(maskEmail('@example.com')).toBe('***@example.com');
});
it('masks email with two chars before @', () => {
expect(maskEmail('ab@example.com')).toBe('a***@example.com');
});
it('handles email without @', () => {
expect(maskEmail('noemail')).toBe('***@noemail');
});
});
});