Initial commit: breakpilot-compliance - Compliance SDK Platform

Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -0,0 +1,312 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ConsentAPI } from './ConsentAPI';
import type { ConsentConfig, ConsentState } from '../types';
describe('ConsentAPI', () => {
let api: ConsentAPI;
const mockConfig: ConsentConfig = {
apiEndpoint: 'https://api.example.com/',
siteId: 'test-site',
debug: false,
};
const mockConsent: ConsentState = {
categories: {
essential: true,
functional: true,
analytics: false,
marketing: false,
social: false,
},
vendors: {},
timestamp: '2024-01-15T10:00:00.000Z',
version: '1.0.0',
};
beforeEach(() => {
api = new ConsentAPI(mockConfig);
vi.clearAllMocks();
});
describe('constructor', () => {
it('should strip trailing slash from apiEndpoint', () => {
expect(api).toBeDefined();
});
});
describe('saveConsent', () => {
it('should POST consent to the API', async () => {
const mockResponse = {
consentId: 'consent-123',
timestamp: '2024-01-15T10:00:00.000Z',
expiresAt: '2025-01-15T10:00:00.000Z',
};
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve(mockResponse),
} as Response);
const result = await api.saveConsent({
siteId: 'test-site',
deviceFingerprint: 'fp_123',
consent: mockConsent,
});
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith(
'https://api.example.com/consent',
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'Content-Type': 'application/json',
Accept: 'application/json',
}),
credentials: 'include',
})
);
expect(result.consentId).toBe('consent-123');
});
it('should include metadata in the request', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve({ consentId: '123' }),
} as Response);
await api.saveConsent({
siteId: 'test-site',
deviceFingerprint: 'fp_123',
consent: mockConsent,
});
const call = vi.mocked(fetch).mock.calls[0];
const body = JSON.parse(call[1]?.body as string);
expect(body.metadata).toBeDefined();
expect(body.metadata.platform).toBe('web');
});
it('should throw on non-ok response', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 500,
} as Response);
await expect(
api.saveConsent({
siteId: 'test-site',
deviceFingerprint: 'fp_123',
consent: mockConsent,
})
).rejects.toThrow('Failed to save consent: 500');
});
it('should include signature headers', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve({ consentId: '123' }),
} as Response);
await api.saveConsent({
siteId: 'test-site',
deviceFingerprint: 'fp_123',
consent: mockConsent,
});
const call = vi.mocked(fetch).mock.calls[0];
const headers = call[1]?.headers as Record<string, string>;
expect(headers['X-Consent-Timestamp']).toBeDefined();
expect(headers['X-Consent-Signature']).toMatch(/^sha256=/);
});
});
describe('getConsent', () => {
it('should GET consent from the API', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve({ consent: mockConsent }),
} as Response);
const result = await api.getConsent('test-site', 'fp_123');
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/consent?siteId=test-site&deviceFingerprint=fp_123'),
expect.objectContaining({
headers: expect.any(Object),
credentials: 'include',
})
);
expect(result?.categories.essential).toBe(true);
});
it('should return null on 404', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 404,
} as Response);
const result = await api.getConsent('test-site', 'fp_123');
expect(result).toBeNull();
});
it('should throw on other errors', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 500,
} as Response);
await expect(api.getConsent('test-site', 'fp_123')).rejects.toThrow(
'Failed to get consent: 500'
);
});
});
describe('revokeConsent', () => {
it('should DELETE consent from the API', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 204,
} as Response);
await api.revokeConsent('consent-123');
expect(fetch).toHaveBeenCalledWith(
'https://api.example.com/consent/consent-123',
expect.objectContaining({
method: 'DELETE',
})
);
});
it('should throw on non-ok response', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 404,
} as Response);
await expect(api.revokeConsent('consent-123')).rejects.toThrow(
'Failed to revoke consent: 404'
);
});
});
describe('getSiteConfig', () => {
it('should GET site configuration', async () => {
const mockSiteConfig = {
siteId: 'test-site',
name: 'Test Site',
categories: ['essential', 'analytics'],
};
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve(mockSiteConfig),
} as Response);
const result = await api.getSiteConfig('test-site');
expect(fetch).toHaveBeenCalledWith(
'https://api.example.com/config/test-site',
expect.any(Object)
);
expect(result.siteId).toBe('test-site');
});
it('should throw on error', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 404,
} as Response);
await expect(api.getSiteConfig('unknown-site')).rejects.toThrow(
'Failed to get site config: 404'
);
});
});
describe('exportConsent', () => {
it('should GET consent export for user', async () => {
const mockExport = {
userId: 'user-123',
consents: [mockConsent],
};
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve(mockExport),
} as Response);
const result = await api.exportConsent('user-123');
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/consent/export?userId=user-123'),
expect.any(Object)
);
expect(result).toEqual(mockExport);
});
it('should throw on error', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 403,
} as Response);
await expect(api.exportConsent('user-123')).rejects.toThrow(
'Failed to export consent: 403'
);
});
});
describe('network errors', () => {
it('should propagate fetch errors', async () => {
vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error'));
await expect(
api.saveConsent({
siteId: 'test-site',
deviceFingerprint: 'fp_123',
consent: mockConsent,
})
).rejects.toThrow('Network error');
});
});
describe('debug mode', () => {
it('should log when debug is enabled', async () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const debugApi = new ConsentAPI({
...mockConfig,
debug: true,
});
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve({ consentId: '123' }),
} as Response);
await debugApi.saveConsent({
siteId: 'test-site',
deviceFingerprint: 'fp_123',
consent: mockConsent,
});
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
});