fix: fix remaining integration test issues
- Fix user-profile tests to use apiKeyClient instead of session auth - Fix stats test for new nested response structure (data.overview.*) - Enable conversation tests with CI OpenAI key setup - Add setup-openai-key.ts script to configure test user's OPENAI_API_KEY - Update workflow to run OpenAI key setup before tests
This commit is contained in:
parent
a8a52c972c
commit
907ac2301b
7 changed files with 141 additions and 26 deletions
8
.github/workflows/integration-tests.yml
vendored
8
.github/workflows/integration-tests.yml
vendored
|
|
@ -50,6 +50,14 @@ jobs:
|
|||
INTEGRATION_TEST_USER_ID: ${{ secrets.INTEGRATION_TEST_USER_ID }}
|
||||
run: pnpm --filter=@tpmjs/web test:cleanup-orphans
|
||||
|
||||
- name: Setup OpenAI key for test user
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
API_KEY_ENCRYPTION_SECRET: ${{ secrets.API_KEY_ENCRYPTION_SECRET }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
INTEGRATION_TEST_USER_ID: ${{ secrets.INTEGRATION_TEST_USER_ID }}
|
||||
run: pnpm --filter=@tpmjs/web test:setup-openai-key
|
||||
|
||||
- name: Wait for API
|
||||
run: |
|
||||
echo "Checking if API is available at $TEST_BASE_URL..."
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"test:integration": "INTEGRATION_TESTS=true vitest run --config vitest.integration.config.ts",
|
||||
"test:integration:watch": "INTEGRATION_TESTS=true vitest --config vitest.integration.config.ts",
|
||||
"test:setup-credentials": "tsx src/test/integration/_helpers/setup-test-credentials.ts",
|
||||
"test:setup-openai-key": "tsx src/test/integration/_helpers/setup-openai-key.ts",
|
||||
"test:cleanup-orphans": "tsx src/test/integration/_helpers/cleanup-orphans.ts",
|
||||
"generate-og": "tsx scripts/generate-og-images.ts"
|
||||
},
|
||||
|
|
|
|||
78
apps/web/src/test/integration/_helpers/setup-openai-key.ts
Normal file
78
apps/web/src/test/integration/_helpers/setup-openai-key.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env tsx
|
||||
/**
|
||||
* Setup script to store OPENAI_API_KEY for integration tests
|
||||
*
|
||||
* This script should be run in CI before the integration tests to ensure
|
||||
* the test user has an OPENAI_API_KEY configured for agent conversation tests.
|
||||
*
|
||||
* Required environment variables:
|
||||
* - DATABASE_URL: Neon PostgreSQL connection string
|
||||
* - API_KEY_ENCRYPTION_SECRET: Encryption secret for API keys
|
||||
* - OPENAI_API_KEY: The OpenAI API key to store
|
||||
* - INTEGRATION_TEST_USER_ID: The test user's ID
|
||||
*/
|
||||
|
||||
import { resolve } from 'node:path';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
config({ path: resolve(__dirname, '../../../../.env.local') });
|
||||
config({ path: resolve(__dirname, '../../../../.env') });
|
||||
|
||||
// Validate required env vars
|
||||
const requiredEnvVars = [
|
||||
'DATABASE_URL',
|
||||
'API_KEY_ENCRYPTION_SECRET',
|
||||
'OPENAI_API_KEY',
|
||||
'INTEGRATION_TEST_USER_ID',
|
||||
];
|
||||
|
||||
for (const envVar of requiredEnvVars) {
|
||||
if (!process.env[envVar]) {
|
||||
console.log(`Skipping: ${envVar} not set`);
|
||||
process.exit(0); // Exit gracefully - tests that need this will be skipped
|
||||
}
|
||||
}
|
||||
|
||||
// Import after env validation
|
||||
import { prisma } from '@tpmjs/db';
|
||||
import { encryptApiKey } from '@/lib/crypto/api-keys';
|
||||
|
||||
async function main() {
|
||||
const userId = process.env.INTEGRATION_TEST_USER_ID!;
|
||||
const openaiKey = process.env.OPENAI_API_KEY!;
|
||||
|
||||
console.log('Setting up OPENAI_API_KEY for integration test user...');
|
||||
|
||||
try {
|
||||
// Delete existing key if any
|
||||
await prisma.userApiKey.deleteMany({
|
||||
where: {
|
||||
userId,
|
||||
keyName: 'OPENAI_API_KEY',
|
||||
},
|
||||
});
|
||||
|
||||
// Encrypt and store the key
|
||||
const { encrypted, iv } = encryptApiKey(openaiKey);
|
||||
await prisma.userApiKey.create({
|
||||
data: {
|
||||
userId,
|
||||
keyName: 'OPENAI_API_KEY',
|
||||
encryptedKey: encrypted,
|
||||
keyIv: iv,
|
||||
keyHint: openaiKey.slice(-4),
|
||||
},
|
||||
});
|
||||
|
||||
console.log('OPENAI_API_KEY configured for test user');
|
||||
} catch (error) {
|
||||
console.error('Failed to setup OPENAI_API_KEY:', error);
|
||||
// Don't fail the build - tests will be skipped if key isn't available
|
||||
process.exit(0);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -117,7 +117,39 @@ async function main() {
|
|||
});
|
||||
console.log(' API key created');
|
||||
|
||||
// 4. Output credentials
|
||||
// 4. Store OPENAI_API_KEY for agent conversations (if available)
|
||||
const openaiKey = process.env.OPENAI_API_KEY;
|
||||
if (openaiKey) {
|
||||
console.log('\n📝 Storing OPENAI_API_KEY for agent conversations...');
|
||||
|
||||
// Import encryption function
|
||||
const { encryptApiKey } = await import('@/lib/crypto/api-keys');
|
||||
|
||||
// Delete existing key if any
|
||||
await prisma.userApiKey.deleteMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
keyName: 'OPENAI_API_KEY',
|
||||
},
|
||||
});
|
||||
|
||||
// Encrypt and store the key
|
||||
const { encrypted, iv } = encryptApiKey(openaiKey);
|
||||
await prisma.userApiKey.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
keyName: 'OPENAI_API_KEY',
|
||||
encryptedKey: encrypted,
|
||||
keyIv: iv,
|
||||
keyHint: openaiKey.slice(-4),
|
||||
},
|
||||
});
|
||||
console.log(' OPENAI_API_KEY stored');
|
||||
} else {
|
||||
console.log('\n⚠️ OPENAI_API_KEY not set - agent conversation tests will be skipped');
|
||||
}
|
||||
|
||||
// 5. Output credentials
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('🎉 Integration test credentials generated!\n');
|
||||
console.log('Add these to your .env.local or GitHub secrets:\n');
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@
|
|||
*
|
||||
* Tests the agent chat/conversation endpoints with streaming responses.
|
||||
*
|
||||
* NOTE: Tests that make actual AI calls are skipped because they require
|
||||
* the test user to have AI provider API keys (OPENAI_API_KEY, etc.) configured.
|
||||
* These tests would work in a fully configured test environment but are not
|
||||
* suitable for CI/CD against production databases.
|
||||
* NOTE: These tests require the test user to have OPENAI_API_KEY configured
|
||||
* in the database. The CI workflow sets this up via test:setup-openai-key.
|
||||
*/
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
|
@ -34,8 +32,7 @@ describe('Agent Conversations Endpoints', () => {
|
|||
});
|
||||
|
||||
describe('POST /api/:username/agents/:uid/conversation/:conversationId', () => {
|
||||
// Skip: Requires AI provider API keys configured for the test user
|
||||
it.skip('should create a conversation and receive streaming response', async () => {
|
||||
it('should create a conversation and receive streaming response', async () => {
|
||||
if (!testAgent) {
|
||||
console.log('Skipping: No test agent available');
|
||||
return;
|
||||
|
|
@ -69,8 +66,7 @@ describe('Agent Conversations Endpoints', () => {
|
|||
}
|
||||
});
|
||||
|
||||
// Skip: Requires AI provider API keys configured for the test user
|
||||
it.skip('should maintain conversation context on follow-up messages', async () => {
|
||||
it('should maintain conversation context on follow-up messages', async () => {
|
||||
if (!testAgent) {
|
||||
console.log('Skipping: No test agent available');
|
||||
return;
|
||||
|
|
@ -117,8 +113,7 @@ describe('Agent Conversations Endpoints', () => {
|
|||
});
|
||||
|
||||
describe('GET /api/:username/agents/:uid/conversation/:conversationId', () => {
|
||||
// Skip: Requires creating a conversation first, which needs AI provider API keys
|
||||
it.skip('should retrieve conversation history', async () => {
|
||||
it('should retrieve conversation history', async () => {
|
||||
if (!testAgent) {
|
||||
console.log('Skipping: No test agent available');
|
||||
return;
|
||||
|
|
@ -170,8 +165,7 @@ describe('Agent Conversations Endpoints', () => {
|
|||
});
|
||||
|
||||
describe('GET /api/:username/agents/:uid/conversations', () => {
|
||||
// Skip: Requires creating a conversation first, which needs AI provider API keys
|
||||
it.skip('should list all conversations for an agent', async () => {
|
||||
it('should list all conversations for an agent', async () => {
|
||||
if (!testAgent) {
|
||||
console.log('Skipping: No test agent available');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ describe('Stats Endpoints', () => {
|
|||
const result = await ctx.publicClient.get<{
|
||||
success: boolean;
|
||||
data: {
|
||||
totalTools: number;
|
||||
totalPackages: number;
|
||||
officialTools: number;
|
||||
toolsWithSchema: number;
|
||||
healthyTools: number;
|
||||
brokenTools: number;
|
||||
overview: {
|
||||
totalTools: number;
|
||||
totalPackages: number;
|
||||
officialTools: number;
|
||||
};
|
||||
health: {
|
||||
import: { healthy: number; broken: number };
|
||||
};
|
||||
};
|
||||
}>('/api/stats');
|
||||
|
||||
|
|
@ -36,8 +38,8 @@ describe('Stats Endpoints', () => {
|
|||
if (result.ok) {
|
||||
expect(result.data.success).toBe(true);
|
||||
expect(result.data.data).toBeDefined();
|
||||
expect(result.data.data.totalTools).toBeGreaterThanOrEqual(0);
|
||||
expect(result.data.data.totalPackages).toBeGreaterThanOrEqual(0);
|
||||
expect(result.data.data.overview.totalTools).toBeGreaterThanOrEqual(0);
|
||||
expect(result.data.data.overview.totalPackages).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ describe('User Profile Endpoints', () => {
|
|||
});
|
||||
|
||||
describe('GET /api/user/profile', () => {
|
||||
it('should return user profile with session auth', async () => {
|
||||
const result = await ctx.api.get<{
|
||||
it('should return user profile with API key auth', async () => {
|
||||
const result = await ctx.apiKeyClient.get<{
|
||||
success: boolean;
|
||||
data: UserProfile;
|
||||
}>('/api/user/profile');
|
||||
|
|
@ -54,7 +54,7 @@ describe('User Profile Endpoints', () => {
|
|||
describe('PATCH /api/user/profile', () => {
|
||||
it('should update user profile', async () => {
|
||||
// First, get current profile
|
||||
const profileResult = await ctx.api.get<{
|
||||
const profileResult = await ctx.apiKeyClient.get<{
|
||||
success: boolean;
|
||||
data: UserProfile;
|
||||
}>('/api/user/profile');
|
||||
|
|
@ -65,7 +65,7 @@ describe('User Profile Endpoints', () => {
|
|||
const originalName = profileResult.data.data.name;
|
||||
|
||||
// Update name
|
||||
const updateResult = await ctx.api.patch<{
|
||||
const updateResult = await ctx.apiKeyClient.patch<{
|
||||
success: boolean;
|
||||
data: UserProfile;
|
||||
}>('/api/user/profile', {
|
||||
|
|
@ -78,7 +78,7 @@ describe('User Profile Endpoints', () => {
|
|||
}
|
||||
|
||||
// Restore original name
|
||||
await ctx.api.patch('/api/user/profile', {
|
||||
await ctx.apiKeyClient.patch('/api/user/profile', {
|
||||
name: originalName,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue