Testing Guide
Complete guide to testing SCORM API integrations.
Table of Contents
- Overview
- Testing Strategies
- Unit Testing
- Integration Testing
- End-to-End Testing
- Test Data Management
- Best Practices
Overview
Testing ensures:
- API integration works correctly
- Error handling is robust
- Performance meets requirements
- Security is maintained
Testing Strategies
Test Pyramid
/\
/ \ E2E Tests (Few)
/____\
/ \ Integration Tests (Some)
/________\
/ \ Unit Tests (Many)
/____________\
Test Types
- Unit Tests: Test individual functions/components
- Integration Tests: Test API interactions
- E2E Tests: Test complete workflows
- Performance Tests: Test under load
- Security Tests: Test security measures
Unit Testing
Testing API Client
// __tests__/scorm-client.test.ts
import { ScormAPIClient } from '../lib/scorm-client';
describe('ScormAPIClient', () => {
let client: ScormAPIClient;
beforeEach(() => {
client = new ScormAPIClient(
'test-api-key',
'https://test-api.example.com',
'test-tenant-id'
);
});
test('uploadPackage creates FormData correctly', async () => {
const file = new File(['test'], 'test.zip');
const formData = client.createFormData(file, 'user-123');
expect(formData.get('file')).toBe(file);
expect(formData.get('tenant_id')).toBe('test-tenant-id');
expect(formData.get('uploaded_by')).toBe('user-123');
});
test('handles version conflicts', async () => {
// Mock fetch to return 409
global.fetch = jest.fn()
.mockResolvedValueOnce({
status: 409,
json: async () => ({ error: 'Version conflict' })
})
.mockResolvedValueOnce({
status: 200,
json: async () => ({ id: 'session-123', version: 2 })
});
const result = await client.updateSessionWithRetry(
'session-123',
{ completion_status: 'completed' }
);
expect(result.version).toBe(2);
expect(global.fetch).toHaveBeenCalledTimes(2);
});
});
Testing Error Handling
describe('Error Handling', () => {
test('handles network errors', async () => {
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
await expect(
client.uploadPackage(new File(['test'], 'test.zip'), 'user-123')
).rejects.toThrow('Network error');
});
test('handles rate limiting', async () => {
global.fetch = jest.fn().mockResolvedValue({
status: 429,
headers: new Headers({ 'Retry-After': '60' })
});
// Should retry after delay
const start = Date.now();
await client.makeRequest('/api/v1/packages');
const duration = Date.now() - start;
expect(duration).toBeGreaterThan(50000); // Should wait ~60s
});
});
Integration Testing
Testing Package Upload
Integration tests should follow the presigned flow against a real or staging tenant (https://app.allureconnect.com): POST /api/v1/packages/upload-url → PUT → POST /api/v1/packages/process. Do not assert on a multipart POST /api/v1/packages — that path is not the supported upload surface.
// __tests__/integration/package-upload.test.ts
describe('Package Upload Integration', () => {
const base = process.env.TEST_API_BASE ?? 'https://app.allureconnect.com';
const apiKey = process.env.TEST_API_KEY!;
const tenantId = process.env.TEST_TENANT_ID!;
const auth = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' };
test('mints upload URL and rejects invalid filename', async () => {
const mint = await fetch(`${base}/api/v1/packages/upload-url`, {
method: 'POST',
headers: auth,
body: JSON.stringify({
tenant_id: tenantId,
uploaded_by: 'test-user',
filename: 'not-a-zip.txt',
file_size: 12,
}),
});
expect(mint.status).toBeGreaterThanOrEqual(400);
});
// Happy-path tests need a real ZIP bytes fixture and storage PUT permissions.
});
Testing Session Management
describe('Session Management Integration', () => {
test('creates and tracks session', async () => {
// 1. Create session
const launchResponse = await fetch(
`https://app.allureconnect.com/api/v1/packages/${packageId}/launch`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_id: 'test-user',
session_id: 'test-session'
})
}
);
const launch = await launchResponse.json();
expect(launch.session_id).toBe('test-session');
// 2. Get session
const sessionResponse = await fetch(
`https://app.allureconnect.com/api/v1/sessions/test-session`,
{
headers: { Authorization: `Bearer ${apiKey}` }
}
);
const session = await sessionResponse.json();
expect(session.id).toBe('test-session');
expect(session.completion_status).toBe('not_attempted');
// 3. Update session
const updateResponse = await fetch(
`https://app.allureconnect.com/api/v1/sessions/test-session`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
version: session.version,
completion_status: 'completed'
})
}
);
expect(updateResponse.status).toBe(200);
});
});
End-to-End Testing
Complete Workflow Test
describe('Complete SCORM Workflow', () => {
test('upload, launch, track, complete', async () => {
const client = new ScormAPIClient(apiKey, baseUrl, tenantId);
// 1. Upload package
const package = await client.uploadPackage(testPackageFile, 'test-user');
expect(package.id).toBeDefined();
// 2. Wait for processing
await waitForProcessing(package.id, 30000); // 30s timeout
// 3. Launch session
const launch = await client.launchSession(package.id, 'test-user');
expect(launch.launch_url).toBeDefined();
// 4. Track progress
let session = await client.getSession(launch.session_id);
expect(session.completion_status).toBe('not_attempted');
// 5. Simulate progress
await client.updateSession(launch.session_id, {
version: session.version,
completion_status: 'completed',
score: { scaled: 0.85 }
});
// 6. Verify completion
session = await client.getSession(launch.session_id);
expect(session.completion_status).toBe('completed');
expect(session.score.scaled).toBe(0.85);
});
});
Testing with Real SCORM Package
describe('Real SCORM Package Test', () => {
test('processes real SCORM 1.2 package', async () => {
const packageFile = await fs.readFile('test-packages/scorm12-test.zip');
const uploadResponse = await uploadPackage(packageFile);
expect(uploadResponse.status).toBe(200);
const package = await uploadResponse.json();
// Wait for processing
await pollUntilProcessed(package.id, {
timeout: 60000,
interval: 2000
});
// Verify package details
const packageDetails = await getPackage(package.id);
expect(packageDetails.version).toBe('1.2');
expect(packageDetails.launch_url).toBeDefined();
});
});
Test Data Management
Test Fixtures
// __tests__/fixtures/packages.ts
export const testPackages = {
scorm12: {
file: 'test-packages/scorm12-test.zip',
expectedVersion: '1.2',
expectedTitle: 'Test SCORM 1.2 Package'
},
scorm2004: {
file: 'test-packages/scorm2004-test.zip',
expectedVersion: '2004',
expectedTitle: 'Test SCORM 2004 Package'
}
};
Test Utilities
// __tests__/utils/test-helpers.ts
export async function createTestPackage() {
const response = await uploadPackage(testPackages.scorm12.file);
const pkg = await response.json();
// Wait for processing
await waitForProcessing(pkg.id);
return pkg;
}
export async function createTestSession(packageId: string, userId: string) {
const response = await launchSession(packageId, userId);
return response.json();
}
export async function cleanupTestData(packageId: string) {
// Delete test package
await deletePackage(packageId);
}
Test Isolation
describe('Package Management', () => {
let testPackageId: string;
beforeEach(async () => {
// Create test package for each test
const pkg = await createTestPackage();
testPackageId = pkg.id;
});
afterEach(async () => {
// Cleanup after each test
await cleanupTestData(testPackageId);
});
test('gets package details', async () => {
const pkg = await getPackage(testPackageId);
expect(pkg.id).toBe(testPackageId);
});
});
Best Practices
1. Use Test Environment
// Use separate test environment
const baseUrl = process.env.TEST_API_URL || 'https://test-api.allurelms.com';
const apiKey = process.env.TEST_API_KEY;
2. Mock External Dependencies
// Mock fetch for unit tests
global.fetch = jest.fn().mockResolvedValue({
status: 200,
json: async () => ({ id: 'test-id' })
});
3. Test Error Scenarios
test('handles all error codes', async () => {
const errorCodes = [400, 401, 403, 404, 409, 429, 500];
for (const code of errorCodes) {
global.fetch = jest.fn().mockResolvedValue({
status: code,
json: async () => ({ error: 'Test error', code: `ERROR_${code}` })
});
await expect(client.makeRequest('/test')).rejects.toThrow();
}
});
4. Test Performance
test('meets performance requirements', async () => {
const start = Date.now();
await client.uploadPackage(testFile, 'user-123');
const duration = Date.now() - start;
expect(duration).toBeLessThan(5000); // Should complete in <5s
});
5. Test Security
test('prevents cross-tenant access', async () => {
const otherTenantPackage = 'other-tenant-package-id';
await expect(
client.getPackage(otherTenantPackage)
).rejects.toThrow('Package not found');
});
Testing Checklist
- Unit tests for API client
- Integration tests for all endpoints
- E2E tests for complete workflows
- Error handling tests
- Performance tests
- Security tests
- Test data cleanup
- Test isolation
- Mock external dependencies
- Test with real SCORM packages
Related Documentation
- API Reference - Complete API docs
- Error Handling Guide - Error handling
- Best Practices - Integration best practices
Last Updated: 2025-01-15