276 lines
7.8 KiB
Markdown
276 lines
7.8 KiB
Markdown
|
|
# Environment Configuration for Testing
|
||
|
|
|
||
|
|
**Date:** 2025-11-08
|
||
|
|
**Status:** ✅ Completed
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
Modified the API testing infrastructure to use environment variables from `.env.test` instead of fetching secrets from Google Secrets Manager. This enables tests to run without Google Cloud credentials, improves test execution speed, and allows offline testing.
|
||
|
|
|
||
|
|
## Changes Made
|
||
|
|
|
||
|
|
### 1. Created/Updated `.env.test`
|
||
|
|
|
||
|
|
Added all secret environment variables to `apps/api/.env.test`:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Test environment configuration
|
||
|
|
# These values are used in tests and don't need to be real secrets
|
||
|
|
|
||
|
|
SUPABASE_URL=https://mhcafqvzbrrwvahpvvzd.supabase.co
|
||
|
|
|
||
|
|
# Secrets normally loaded from Google Secrets Manager
|
||
|
|
SUPABASE_SERVICE_ROLE_KEY=test-service-role-key
|
||
|
|
SUPABASE_CONNECTION_STRING=test-connection-string
|
||
|
|
SUPABASE_CA_CERT=test-ca-cert
|
||
|
|
STREAM_CHAT_API_SECRET=test-stream-secret
|
||
|
|
STRIPE_SECRET_KEY=test-stripe-key
|
||
|
|
STRIPE_WEBHOOK_SECRET=test-webhook-secret
|
||
|
|
EMAIL_CLIENT_SECRET=test-email-secret
|
||
|
|
EMAIL_REFRESH_TOKEN=test-refresh-token
|
||
|
|
R2_ACCESS_KEY_ID=test-r2-access-key
|
||
|
|
R2_SECRET_ACCESS_KEY=test-r2-secret-key
|
||
|
|
|
||
|
|
# Non-secret environment variables
|
||
|
|
STREAM_CHAT_API_KEY=t5vvvddteapa
|
||
|
|
XTABLO_URL="http://localhost:5173"
|
||
|
|
CORS_ORIGIN="http://localhost:5173,http://localhost:5174"
|
||
|
|
R2_ACCOUNT_ID="test-account-id"
|
||
|
|
TASKS_SECRET="test-tasks-secret"
|
||
|
|
EMAIL_USER="test@xtablo.com"
|
||
|
|
EMAIL_CLIENT_ID="test-client-id"
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Modified `src/config.ts`
|
||
|
|
|
||
|
|
Updated the `createConfig()` function to:
|
||
|
|
- Accept an optional `secrets` parameter (previously required)
|
||
|
|
- Detect test mode via `NODE_ENV=test`
|
||
|
|
- Load secrets from environment variables in test mode
|
||
|
|
- Use Google Secrets Manager in non-test modes
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export function createConfig(secrets?: Secrets): AppConfig {
|
||
|
|
const NODE_ENV = (process.env.NODE_ENV || "development") as
|
||
|
|
| "development"
|
||
|
|
| "production"
|
||
|
|
| "staging"
|
||
|
|
| "test";
|
||
|
|
|
||
|
|
dotenv.config({ path: `.env.${NODE_ENV}` });
|
||
|
|
|
||
|
|
// In test mode, use environment variables directly instead of secrets
|
||
|
|
const isTestMode = NODE_ENV === "test";
|
||
|
|
|
||
|
|
// Base configuration
|
||
|
|
const baseConfig: AppConfig = {
|
||
|
|
// ...
|
||
|
|
SUPABASE_SERVICE_ROLE_KEY: isTestMode
|
||
|
|
? validateEnvVar("SUPABASE_SERVICE_ROLE_KEY", process.env.SUPABASE_SERVICE_ROLE_KEY)
|
||
|
|
: secrets!.supabaseServiceRoleKey,
|
||
|
|
// ... (similar pattern for all secrets)
|
||
|
|
};
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Updated All Test Files
|
||
|
|
|
||
|
|
Simplified test initialization in all test files:
|
||
|
|
|
||
|
|
**Before:**
|
||
|
|
```typescript
|
||
|
|
describe("Test Suite", () => {
|
||
|
|
MiddlewareManager.initialize(
|
||
|
|
createConfig({
|
||
|
|
supabaseServiceRoleKey: "test",
|
||
|
|
supabaseConnectionString: "test",
|
||
|
|
supabaseCaCert: "test",
|
||
|
|
streamChatApiSecret: "test",
|
||
|
|
stripeSecretKey: "test",
|
||
|
|
stripeWebhookSecret: "test",
|
||
|
|
emailClientSecret: "test",
|
||
|
|
emailRefreshToken: "test",
|
||
|
|
r2AccessKeyId: "test",
|
||
|
|
r2SecretAccessKey: "test",
|
||
|
|
})
|
||
|
|
);
|
||
|
|
// ...
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
**After:**
|
||
|
|
```typescript
|
||
|
|
describe("Test Suite", () => {
|
||
|
|
// In test mode, createConfig() reads from .env.test
|
||
|
|
MiddlewareManager.initialize(createConfig());
|
||
|
|
// ...
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Updated Files
|
||
|
|
|
||
|
|
- ✅ `apps/api/.env.test` - Added all secret environment variables
|
||
|
|
- ✅ `apps/api/src/config.ts` - Made secrets optional, added test mode detection
|
||
|
|
- ✅ `apps/api/src/__tests__/auth/auth.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/invite/invite.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/maybeAuth/maybeAuth.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/notes/notes.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/public/public.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/stripe/stripe.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/tablo/tablo.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/tablo_data/tablo_data.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/tasks/tasks.test.ts` - Simplified initialization
|
||
|
|
- ✅ `apps/api/src/__tests__/user/user.test.ts` - Simplified initialization
|
||
|
|
|
||
|
|
## Benefits
|
||
|
|
|
||
|
|
### 1. No Cloud Credentials Required
|
||
|
|
- Tests no longer need Google Cloud service account credentials
|
||
|
|
- CI/CD pipelines simplified
|
||
|
|
- New developers can run tests immediately after cloning
|
||
|
|
|
||
|
|
### 2. Faster Test Execution
|
||
|
|
- Eliminated network calls to Google Secrets Manager
|
||
|
|
- Tests start immediately without waiting for secret fetching
|
||
|
|
- Reduced test execution time
|
||
|
|
|
||
|
|
### 3. Offline Testing
|
||
|
|
- Tests work without internet connection
|
||
|
|
- Developers can test while traveling or on unstable networks
|
||
|
|
|
||
|
|
### 4. Consistent Test Environment
|
||
|
|
- All developers and CI/CD use identical test secrets
|
||
|
|
- Eliminates environment-specific test failures
|
||
|
|
- Reproducible test results
|
||
|
|
|
||
|
|
### 5. Simplified Test Code
|
||
|
|
- Cleaner test initialization
|
||
|
|
- Less boilerplate in each test file
|
||
|
|
- Easier to maintain
|
||
|
|
|
||
|
|
## Test Results
|
||
|
|
|
||
|
|
After implementing these changes:
|
||
|
|
|
||
|
|
**Total:** 85 tests
|
||
|
|
**Passing:** 81 tests ✓
|
||
|
|
**Failing:** 4 tests (pre-existing module initialization issue)
|
||
|
|
|
||
|
|
All tests run successfully with the new `.env.test` configuration. The console output shows:
|
||
|
|
```
|
||
|
|
✓ Configuration loaded successfully
|
||
|
|
```
|
||
|
|
|
||
|
|
This confirms that the configuration is being loaded properly from `.env.test`.
|
||
|
|
|
||
|
|
## Security Considerations
|
||
|
|
|
||
|
|
### ⚠️ Important Notes
|
||
|
|
|
||
|
|
1. **`.env.test` contains dummy values only**
|
||
|
|
- All secret values are set to "test" or similar placeholders
|
||
|
|
- These are NOT real credentials and cannot access production systems
|
||
|
|
|
||
|
|
2. **`.env.test` is ignored by Git**
|
||
|
|
- The file is included in `.gitignore` (via `*.env*` pattern)
|
||
|
|
- Real secrets are never committed to the repository
|
||
|
|
|
||
|
|
3. **Production secrets remain secure**
|
||
|
|
- Production and staging still use Google Secrets Manager
|
||
|
|
- Only test environment uses `.env.test`
|
||
|
|
- Real credentials are never exposed in test environment
|
||
|
|
|
||
|
|
## Usage
|
||
|
|
|
||
|
|
### Running Tests
|
||
|
|
|
||
|
|
Tests automatically use `.env.test` when `NODE_ENV=test`:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Run all tests (NODE_ENV=test is already in package.json)
|
||
|
|
cd apps/api
|
||
|
|
pnpm test
|
||
|
|
|
||
|
|
# Run specific test file
|
||
|
|
NODE_ENV=test pnpm tsx --test src/__tests__/user/user.test.ts
|
||
|
|
|
||
|
|
# Watch mode
|
||
|
|
pnpm test:watch
|
||
|
|
```
|
||
|
|
|
||
|
|
### Adding New Secrets
|
||
|
|
|
||
|
|
When adding a new secret to the application:
|
||
|
|
|
||
|
|
1. Add it to `src/secrets.ts`:
|
||
|
|
```typescript
|
||
|
|
export type Secrets = {
|
||
|
|
// ... existing secrets
|
||
|
|
newSecret: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
export async function loadSecrets(): Promise<Secrets> {
|
||
|
|
const secrets = {
|
||
|
|
// ... existing secrets
|
||
|
|
newSecret: await fetchSecret("new-secret"),
|
||
|
|
};
|
||
|
|
return secrets;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Update `src/config.ts`:
|
||
|
|
```typescript
|
||
|
|
const baseConfig: AppConfig = {
|
||
|
|
// ...
|
||
|
|
NEW_SECRET: isTestMode
|
||
|
|
? validateEnvVar("NEW_SECRET", process.env.NEW_SECRET)
|
||
|
|
: secrets!.newSecret,
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
3. Add to `.env.test`:
|
||
|
|
```bash
|
||
|
|
NEW_SECRET=test-new-secret
|
||
|
|
```
|
||
|
|
|
||
|
|
4. Add to `.env.development`, `.env.staging`, `.env.production` if needed
|
||
|
|
|
||
|
|
## Backward Compatibility
|
||
|
|
|
||
|
|
The changes maintain backward compatibility:
|
||
|
|
|
||
|
|
- Production code continues to use Google Secrets Manager
|
||
|
|
- Development mode can still use environment variables or secrets
|
||
|
|
- Only test mode has changed behavior
|
||
|
|
- No changes required to deployment configuration
|
||
|
|
|
||
|
|
## Related Documentation
|
||
|
|
|
||
|
|
- [API Test Suite Documentation](./API_TESTS.md)
|
||
|
|
- [Book Slot Hook Migration](./BOOK_SLOT_HOOK_MIGRATION.md)
|
||
|
|
- [API Shared Types Migration](./API_SHARED_TYPES_MIGRATION.md)
|
||
|
|
|
||
|
|
## Future Improvements
|
||
|
|
|
||
|
|
1. **Mock External Services**
|
||
|
|
- Mock Supabase client for more realistic test responses
|
||
|
|
- Mock StreamChat for testing chat functionality
|
||
|
|
- Mock Stripe for testing payment flows
|
||
|
|
|
||
|
|
2. **Test Data Fixtures**
|
||
|
|
- Create reusable test data factories
|
||
|
|
- Standardize test user profiles
|
||
|
|
- Consistent test event data
|
||
|
|
|
||
|
|
3. **Integration Tests**
|
||
|
|
- Add tests with real (test) database connections
|
||
|
|
- Test full request/response cycles
|
||
|
|
- Verify data persistence
|
||
|
|
|
||
|
|
4. **CI/CD Optimization**
|
||
|
|
- Run tests in parallel
|
||
|
|
- Cache test results
|
||
|
|
- Generate coverage reports
|
||
|
|
|