learning_ai_common_plat/packages/feedback-client/src/integration.test.ts
saravanakumardb1 1576b699b0 feat(platform-service): resolve all P3 TODOs — diagnostics notifications + test cleanup
- diagnostics/subscribers: notify admin via email when debug session is
  cancelled (looks up session creator via getSession + getUserById)
- diagnostics/subscribers: email session summary (logs/traces/screenshots)
  to admin when debug session completes
- diagnostics/subscribers: send Slack alert via dispatchSlack for FATAL
  logs ingested during debug sessions (on-call engineer notification)
- feedback-client/integration.test.ts: replace TODO-4 with clear NOTE,
  fix unused var lint errors
- feedback-client/gdpr.test.ts: mark lifecycle policy as accepted,
  remove console.log + unused blobPath variable
- Update WORKSPACE_TODO_AUDIT.md — P3 section: all 5 resolved
- Typecheck clean, 1483/1483 tests pass
2026-03-22 01:03:51 -07:00

211 lines
5.4 KiB
TypeScript

/**
* Integration tests for feedback screenshot flow
*
* These tests verify the complete flow:
* 1. Generate SAS URL for upload
* 2. Upload screenshot to blob storage
* 3. Submit feedback with screenshot metadata
* 4. Retrieve feedback with screenshot URL
*
* NOTE: Requires blob storage to be available in test environment.
* Tests are auto-skipped when AZURE_BLOB_CONNECTION_STRING is not set.
* In CI, set AZURE_BLOB_CONNECTION_STRING or AZURE_BLOB_ACCOUNT_NAME+KEY.
*/
import { describe, it, expect, beforeAll } from 'vitest';
import { createFeedbackClient, type FeedbackClient } from './index.js';
// Check if blob storage is available
const blobStorageAvailable = !!(
process.env.AZURE_BLOB_CONNECTION_STRING ||
(process.env.AZURE_BLOB_ACCOUNT_NAME && process.env.AZURE_BLOB_ACCOUNT_KEY)
);
const describeIntegration = blobStorageAvailable ? describe : describe.skip;
describeIntegration('Feedback Screenshot Integration', () => {
let client: FeedbackClient;
const testBaseUrl = process.env.TEST_API_URL || 'http://localhost:4003';
const testAuthToken = process.env.TEST_AUTH_TOKEN || 'test-token';
beforeAll(() => {
client = createFeedbackClient({
baseUrl: testBaseUrl,
getAuthToken: () => testAuthToken,
});
});
it('should complete full screenshot submission flow', async () => {
// Create a test image blob (1x1 pixel PNG)
const testPngData = new Uint8Array([
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a, // PNG signature
0x00,
0x00,
0x00,
0x0d,
0x49,
0x48,
0x44,
0x52, // IHDR chunk
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01, // 1x1 pixel
0x08,
0x02,
0x00,
0x00,
0x00,
0x90,
0x77,
0x53,
0xde,
0x00,
0x00,
0x00,
0x0c,
0x49,
0x44,
0x41, // IDAT chunk
0x54,
0x08,
0xd7,
0x63,
0xf8,
0xcf,
0xc0,
0x00,
0x00,
0x03,
0x01,
0x01,
0x00,
0x18,
0xdd,
0x8d,
0xb4,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4e, // IEND chunk
0x44,
0xae,
0x42,
0x60,
0x82,
]);
const testBlob = new Blob([testPngData], { type: 'image/png' });
// Submit feedback with screenshot
const result = await client.submitWithScreenshot({
type: 'bug',
title: 'Integration test screenshot',
body: 'This is a test feedback with screenshot',
screenshot: {
blob: testBlob,
contentType: 'image/png',
},
deviceContext: {
osVersion: 'Test OS 1.0',
appVersion: '1.0.0',
deviceModel: 'Test Device',
screenResolution: '1920x1080',
locale: 'en-US',
},
});
// Verify response
expect(result).toBeDefined();
expect(result.id).toBeDefined();
expect(result.type).toBe('bug');
expect(result.title).toBe('Integration test screenshot');
expect(result.status).toBe('new');
expect(result.screenshotBlobPath).toBeDefined();
expect(result.screenshotBlobPath).toContain('feedbackScreenshots');
// TODO: Verify screenshot can be retrieved via admin API
// const screenshotRes = await fetch(`${testBaseUrl}/api/feedback/${result.id}/screenshot`, {
// headers: { Authorization: `Bearer ${adminToken}` },
// });
// expect(screenshotRes.ok).toBe(true);
}, 30000); // 30 second timeout for upload
it('should submit feedback without screenshot', async () => {
const result = await client.submitWithScreenshot({
type: 'feature',
title: 'Integration test without screenshot',
body: 'This is a test feedback without screenshot',
});
expect(result).toBeDefined();
expect(result.id).toBeDefined();
expect(result.type).toBe('feature');
expect(result.screenshotBlobPath).toBeUndefined();
});
it('should track upload progress', async () => {
const progressCallbacks: number[] = [];
const testBlob = new Blob(['test data'], { type: 'image/png' });
try {
await client.submitWithScreenshot(
{
type: 'bug',
title: 'Progress test',
screenshot: {
blob: testBlob,
contentType: 'image/png',
},
},
(loaded, _total) => {
progressCallbacks.push(loaded);
}
);
} catch {
// Expected to fail with invalid PNG, but progress should still be called
}
// Progress callback may or may not be called depending on upload speed
// Just verify the callback mechanism exists
expect(progressCallbacks).toBeDefined();
});
});
describe('Feedback Client Unit Tests (no blob storage required)', () => {
it('should validate screenshot content types', () => {
const validTypes = ['image/png', 'image/jpeg', 'image/webp'];
const invalidTypes = ['image/gif', 'application/pdf', 'text/plain'];
for (const type of validTypes) {
expect(type).toMatch(/^image\/(png|jpeg|webp)$/);
}
for (const type of invalidTypes) {
expect(type).not.toMatch(/^image\/(png|jpeg|webp)$/);
}
});
it('should enforce 5MB size limit', () => {
const maxSize = 5 * 1024 * 1024; // 5MB
const underLimit = maxSize - 1;
const overLimit = maxSize + 1;
expect(underLimit).toBeLessThanOrEqual(maxSize);
expect(overLimit).toBeGreaterThan(maxSize);
});
});