mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 15:51:12 -05:00
Implement integration testing system
This commit is contained in:
259
src/lib/integrationTests/suites/authTests.ts
Normal file
259
src/lib/integrationTests/suites/authTests.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Authentication & Authorization Test Suite
|
||||
*
|
||||
* Tests auth flows, MFA enforcement, role checks, and session management.
|
||||
*/
|
||||
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import type { TestSuite, TestResult } from '../testRunner';
|
||||
|
||||
export const authTestSuite: TestSuite = {
|
||||
id: 'auth',
|
||||
name: 'Authentication & Authorization',
|
||||
description: 'Tests for auth flows, MFA, roles, and permissions',
|
||||
tests: [
|
||||
{
|
||||
id: 'auth-001',
|
||||
name: 'User Session Validation',
|
||||
description: 'Validates current user session is valid with proper JWT structure',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Get current session
|
||||
const { data: { session }, error } = await supabase.auth.getSession();
|
||||
|
||||
if (error) throw new Error(`Session fetch failed: ${error.message}`);
|
||||
if (!session) throw new Error('No active session found');
|
||||
if (!session.access_token) throw new Error('No access token in session');
|
||||
if (!session.user) throw new Error('No user in session');
|
||||
if (!session.user.id) throw new Error('No user ID in session');
|
||||
|
||||
// Validate token structure (JWT has 3 parts separated by dots)
|
||||
const tokenParts = session.access_token.split('.');
|
||||
if (tokenParts.length !== 3) {
|
||||
throw new Error(`Invalid JWT structure: expected 3 parts, got ${tokenParts.length}`);
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if (session.expires_at && session.expires_at < Date.now() / 1000) {
|
||||
throw new Error('Session token is expired');
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
id: 'auth-001',
|
||||
name: 'User Session Validation',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'pass',
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
userId: session.user.id,
|
||||
email: session.user.email,
|
||||
expiresAt: session.expires_at,
|
||||
aal: (session.user as any).aal || 'aal1'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
return {
|
||||
id: 'auth-001',
|
||||
name: 'User Session Validation',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'fail',
|
||||
duration,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'auth-002',
|
||||
name: 'Role-Based Access Control (RBAC)',
|
||||
description: 'Tests role checks are consistent across hooks and database functions',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('No authenticated user');
|
||||
|
||||
// Query user_roles table
|
||||
const { data: roles, error: rolesError } = await supabase
|
||||
.from('user_roles')
|
||||
.select('role')
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (rolesError) throw new Error(`Failed to fetch roles: ${rolesError.message}`);
|
||||
|
||||
// Test is_moderator() database function
|
||||
const { data: isMod, error: modError } = await supabase
|
||||
.rpc('is_moderator', { _user_id: user.id });
|
||||
|
||||
if (modError) throw new Error(`is_moderator() failed: ${modError.message}`);
|
||||
|
||||
// Test is_superuser() database function
|
||||
const { data: isSuper, error: superError } = await supabase
|
||||
.rpc('is_superuser', { _user_id: user.id });
|
||||
|
||||
if (superError) throw new Error(`is_superuser() failed: ${superError.message}`);
|
||||
|
||||
// Validate consistency
|
||||
const hasModRole = roles?.some(r => ['moderator', 'admin', 'superuser'].includes(r.role));
|
||||
if (hasModRole !== isMod) {
|
||||
throw new Error(`Inconsistent moderator check: has role=${hasModRole}, is_moderator()=${isMod}`);
|
||||
}
|
||||
|
||||
const hasSuperRole = roles?.some(r => r.role === 'superuser');
|
||||
if (hasSuperRole !== isSuper) {
|
||||
throw new Error(`Inconsistent superuser check: has role=${hasSuperRole}, is_superuser()=${isSuper}`);
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
id: 'auth-002',
|
||||
name: 'Role-Based Access Control (RBAC)',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'pass',
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
roles: roles?.map(r => r.role) || [],
|
||||
isModerator: isMod,
|
||||
isSuperuser: isSuper,
|
||||
consistent: true
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
return {
|
||||
id: 'auth-002',
|
||||
name: 'Role-Based Access Control (RBAC)',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'fail',
|
||||
duration,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'auth-003',
|
||||
name: 'MFA Factor Detection',
|
||||
description: 'Tests MFA enrollment detection and AAL level',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('No authenticated user');
|
||||
|
||||
// Get MFA factors
|
||||
const { data: factors, error: factorsError } = await supabase.auth.mfa.listFactors();
|
||||
|
||||
if (factorsError) throw new Error(`Failed to list MFA factors: ${factorsError.message}`);
|
||||
|
||||
const hasVerifiedFactor = factors?.totp?.some(f => f.status === 'verified') || false;
|
||||
const currentAAL = (user as any).aal || 'aal1';
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
id: 'auth-003',
|
||||
name: 'MFA Factor Detection',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'pass',
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
hasVerifiedMFA: hasVerifiedFactor,
|
||||
currentAAL: currentAAL,
|
||||
totpFactorCount: factors?.totp?.length || 0,
|
||||
verifiedFactorCount: factors?.totp?.filter(f => f.status === 'verified').length || 0
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
return {
|
||||
id: 'auth-003',
|
||||
name: 'MFA Factor Detection',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'fail',
|
||||
duration,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'auth-004',
|
||||
name: 'Banned User Detection',
|
||||
description: 'Tests banned user detection in profiles table',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('No authenticated user');
|
||||
|
||||
// Query profile banned status
|
||||
const { data: profile, error: profileError } = await supabase
|
||||
.from('profiles')
|
||||
.select('banned')
|
||||
.eq('user_id', user.id)
|
||||
.single();
|
||||
|
||||
if (profileError) throw new Error(`Failed to fetch profile: ${profileError.message}`);
|
||||
if (!profile) throw new Error('No profile found');
|
||||
|
||||
// Test is_user_banned() database function
|
||||
const { data: isBanned, error: bannedError } = await supabase
|
||||
.rpc('is_user_banned', { _user_id: user.id });
|
||||
|
||||
if (bannedError) throw new Error(`is_user_banned() failed: ${bannedError.message}`);
|
||||
|
||||
// Validate consistency
|
||||
if (profile.banned !== isBanned) {
|
||||
throw new Error(`Inconsistent banned check: profile=${profile.banned}, is_user_banned()=${isBanned}`);
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
id: 'auth-004',
|
||||
name: 'Banned User Detection',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'pass',
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
isBanned: profile.banned,
|
||||
consistent: true
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
return {
|
||||
id: 'auth-004',
|
||||
name: 'Banned User Detection',
|
||||
suite: 'Authentication & Authorization',
|
||||
status: 'fail',
|
||||
duration,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user