257 lines
8.9 KiB
JavaScript
257 lines
8.9 KiB
JavaScript
/**
|
|
* Unit Tests: Service Account Authentication
|
|
*
|
|
* Tests T033-T034: Test JWT authentication and credential validation
|
|
* Tests the auth.js module in isolation
|
|
*
|
|
* @module tests/unit/auth
|
|
*/
|
|
|
|
import { describe, it, beforeEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
// =============================================================================
|
|
// T033: Unit test for Service Account JWT authentication
|
|
// =============================================================================
|
|
|
|
describe('T033: Service Account JWT Authentication', () => {
|
|
let originalEnv;
|
|
|
|
beforeEach(() => {
|
|
// Save original env
|
|
originalEnv = process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
});
|
|
|
|
it('should create GoogleAuth client from GOOGLE_SERVICE_ACCOUNT_KEY env var', async () => {
|
|
// Mock credentials as inline JSON (per clarification #1)
|
|
const mockCredentials = {
|
|
type: 'service_account',
|
|
project_id: 'test-project',
|
|
private_key_id: 'key123',
|
|
private_key: '-----BEGIN PRIVATE KEY-----\nMOCK_KEY\n-----END PRIVATE KEY-----\n',
|
|
client_email: 'test@test-project.iam.gserviceaccount.com',
|
|
client_id: '123456789',
|
|
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
|
|
token_uri: 'https://oauth2.googleapis.com/token',
|
|
auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs'
|
|
};
|
|
|
|
// Set env var with inline JSON
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(mockCredentials);
|
|
|
|
// TODO: Import and call initializeAuth from src/auth.js
|
|
// const { initializeAuth } = await import('../../src/auth.js');
|
|
// const auth = await initializeAuth();
|
|
|
|
// Verify GoogleAuth was created with correct credentials
|
|
// assert.ok(auth, 'Should return auth client');
|
|
// assert.equal(auth.credentials.client_email, mockCredentials.client_email, 'Should use client_email from env var');
|
|
|
|
// Restore env
|
|
if (originalEnv) {
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = originalEnv;
|
|
} else {
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
}
|
|
});
|
|
|
|
it('should use correct Drive API scope (read-only)', async () => {
|
|
const mockCredentials = {
|
|
type: 'service_account',
|
|
project_id: 'test-project',
|
|
private_key: '-----BEGIN PRIVATE KEY-----\nMOCK_KEY\n-----END PRIVATE KEY-----\n',
|
|
client_email: 'test@test-project.iam.gserviceaccount.com'
|
|
};
|
|
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(mockCredentials);
|
|
|
|
// TODO: Import and call initializeAuth
|
|
// const { initializeAuth } = await import('../../src/auth.js');
|
|
// const auth = await initializeAuth();
|
|
|
|
// Verify scope is read-only
|
|
const expectedScope = 'https://www.googleapis.com/auth/drive.readonly';
|
|
// assert.ok(auth.scopes.includes(expectedScope), 'Should use drive.readonly scope');
|
|
|
|
// Restore env
|
|
if (originalEnv) {
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = originalEnv;
|
|
} else {
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
}
|
|
});
|
|
|
|
it('should parse inline JSON from env var correctly', async () => {
|
|
// Test with different JSON formatting (whitespace, escaped quotes)
|
|
const mockCredentials = {
|
|
client_email: 'test@project.iam.gserviceaccount.com',
|
|
private_key: '-----BEGIN PRIVATE KEY-----\nMOCK_KEY\n-----END PRIVATE KEY-----\n',
|
|
project_id: 'test-project'
|
|
};
|
|
|
|
// Set with extra whitespace
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(mockCredentials, null, 2);
|
|
|
|
// TODO: Import and call initializeAuth
|
|
// const { initializeAuth } = await import('../../src/auth.js');
|
|
// const auth = await initializeAuth();
|
|
|
|
// Should parse correctly despite formatting
|
|
// assert.ok(auth, 'Should parse JSON with whitespace');
|
|
|
|
// Restore env
|
|
if (originalEnv) {
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = originalEnv;
|
|
} else {
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
}
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// T034: Unit test for credential validation
|
|
// =============================================================================
|
|
|
|
describe('T034: Credential Validation', () => {
|
|
it('should detect missing client_email field', async () => {
|
|
const invalidCredentials = {
|
|
private_key: '-----BEGIN PRIVATE KEY-----\nMOCK_KEY\n-----END PRIVATE KEY-----\n',
|
|
project_id: 'test-project'
|
|
// Missing client_email
|
|
};
|
|
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(invalidCredentials);
|
|
|
|
// TODO: Import validateCredentials from src/auth.js
|
|
// const { validateCredentials } = await import('../../src/auth.js');
|
|
|
|
// Should throw error for missing client_email
|
|
// await assert.rejects(
|
|
// async () => await validateCredentials(invalidCredentials),
|
|
// { message: /client_email/ },
|
|
// 'Should reject credentials without client_email'
|
|
// );
|
|
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
});
|
|
|
|
it('should detect missing private_key field', async () => {
|
|
const invalidCredentials = {
|
|
client_email: 'test@project.iam.gserviceaccount.com',
|
|
project_id: 'test-project'
|
|
// Missing private_key
|
|
};
|
|
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(invalidCredentials);
|
|
|
|
// TODO: Import validateCredentials
|
|
// const { validateCredentials } = await import('../../src/auth.js');
|
|
|
|
// Should throw error for missing private_key
|
|
// await assert.rejects(
|
|
// async () => await validateCredentials(invalidCredentials),
|
|
// { message: /private_key/ },
|
|
// 'Should reject credentials without private_key'
|
|
// );
|
|
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
});
|
|
|
|
it('should detect missing project_id field', async () => {
|
|
const invalidCredentials = {
|
|
client_email: 'test@project.iam.gserviceaccount.com',
|
|
private_key: '-----BEGIN PRIVATE KEY-----\nMOCK_KEY\n-----END PRIVATE KEY-----\n'
|
|
// Missing project_id
|
|
};
|
|
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(invalidCredentials);
|
|
|
|
// TODO: Import validateCredentials
|
|
// const { validateCredentials } = await import('../../src/auth.js');
|
|
|
|
// Should throw error for missing project_id
|
|
// await assert.rejects(
|
|
// async () => await validateCredentials(invalidCredentials),
|
|
// { message: /project_id/ },
|
|
// 'Should reject credentials without project_id'
|
|
// );
|
|
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
});
|
|
|
|
it('should detect empty credential fields', async () => {
|
|
const invalidCredentials = {
|
|
client_email: '', // Empty
|
|
private_key: '-----BEGIN PRIVATE KEY-----\nMOCK_KEY\n-----END PRIVATE KEY-----\n',
|
|
project_id: 'test-project'
|
|
};
|
|
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(invalidCredentials);
|
|
|
|
// TODO: Import validateCredentials
|
|
// const { validateCredentials } = await import('../../src/auth.js');
|
|
|
|
// Should throw error for empty client_email
|
|
// await assert.rejects(
|
|
// async () => await validateCredentials(invalidCredentials),
|
|
// { message: /client_email.*empty/ },
|
|
// 'Should reject empty client_email'
|
|
// );
|
|
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
});
|
|
|
|
it('should accept valid credentials', async () => {
|
|
const validCredentials = {
|
|
type: 'service_account',
|
|
project_id: 'test-project',
|
|
private_key: '-----BEGIN PRIVATE KEY-----\nMOCK_KEY\n-----END PRIVATE KEY-----\n',
|
|
client_email: 'test@test-project.iam.gserviceaccount.com'
|
|
};
|
|
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(validCredentials);
|
|
|
|
// TODO: Import validateCredentials
|
|
// const { validateCredentials } = await import('../../src/auth.js');
|
|
|
|
// Should not throw for valid credentials
|
|
// await assert.doesNotReject(
|
|
// async () => await validateCredentials(validCredentials),
|
|
// 'Should accept valid credentials'
|
|
// );
|
|
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
});
|
|
|
|
it('should trigger fatal error handler on invalid credentials (exit code 1)', async () => {
|
|
// Per T016: Fatal error handler should log to stderr and exit with code 1
|
|
const invalidCredentials = {
|
|
invalid: 'structure'
|
|
};
|
|
|
|
process.env.GOOGLE_SERVICE_ACCOUNT_KEY = JSON.stringify(invalidCredentials);
|
|
|
|
// TODO: Import initializeAuth which should call fatal error handler
|
|
// const { initializeAuth } = await import('../../src/auth.js');
|
|
|
|
// Mock process.exit to prevent actual exit
|
|
// let exitCode;
|
|
// const originalExit = process.exit;
|
|
// process.exit = (code) => { exitCode = code; throw new Error('EXIT'); };
|
|
|
|
// try {
|
|
// await initializeAuth();
|
|
// } catch (e) {
|
|
// if (e.message === 'EXIT') {
|
|
// assert.equal(exitCode, 1, 'Should exit with code 1 on invalid credentials');
|
|
// } else {
|
|
// throw e;
|
|
// }
|
|
// } finally {
|
|
// process.exit = originalExit;
|
|
// }
|
|
|
|
delete process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
});
|
|
});
|