Skip to content

Commit 2a48ec9

Browse files
committed
fix(tests): resolve test failures on GitHub Actions
- Fix WisdomGate quota checker tests: update to use 'session' option instead of 'apiKey' - Fix admin-auth test hanging: remove circular import from index.ts by using env var - Fix quota-enforcer tests: add beforeAll hook to ensure migrations run - Add error handling for missing quota_state table in test cleanup All 714 tests now pass
1 parent 0233055 commit 2a48ec9

5 files changed

Lines changed: 100 additions & 30 deletions

File tree

packages/backend/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ if (!process.env.ADMIN_KEY) {
5858
if (parsed?.adminKey) {
5959
adminKeyFromYaml = parsed.adminKey;
6060
process.env.ADMIN_KEY = adminKeyFromYaml;
61+
process.env.ADMIN_KEY_FROM_YAML = 'true';
6162

6263
// Print large ASCII banner warning
6364
logger.error('');

packages/backend/src/routes/management/__tests__/admin-auth.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import { UsageStorageService } from '../../../services/usage-storage';
99
import { DebugManager } from '../../../services/debug-manager';
1010
import { SelectorFactory } from '../../../services/selectors/factory';
1111

12+
// Helper to close Fastify instances after tests
13+
const closeFastify = async (fastify: FastifyInstance | undefined) => {
14+
if (fastify) {
15+
await fastify.close();
16+
}
17+
};
18+
1219
const BASE_CONFIG = {
1320
providers: {},
1421
models: {},
@@ -76,6 +83,10 @@ describe('GET /v0/management/auth/verify', () => {
7683
await fastify.ready();
7784
});
7885

86+
afterAll(async () => {
87+
await closeFastify(fastify);
88+
});
89+
7990
it('returns 200 with { ok: true } when the correct admin key is provided', async () => {
8091
const res = await fastify.inject({
8192
method: 'GET',
@@ -147,6 +158,10 @@ describe('Management route protection', () => {
147158
await fastify.ready();
148159
});
149160

161+
afterAll(async () => {
162+
await closeFastify(fastify);
163+
});
164+
150165
it('rejects GET /v0/management/cooldowns without admin key', async () => {
151166
const res = await fastify.inject({
152167
method: 'GET',
@@ -261,6 +276,13 @@ describe('v1 inference routes are unaffected by admin key auth', () => {
261276
await fastify.ready();
262277
});
263278

279+
afterAll(async () => {
280+
await closeFastify(fastify);
281+
// Clean up singletons to prevent test hangs
282+
SelectorFactory.setUsageStorage(null as any);
283+
DebugManager.getInstance().setStorage(null as any);
284+
});
285+
264286
it('accepts a v1 request using a valid API key (no admin key needed)', async () => {
265287
const res = await fastify.inject({
266288
method: 'POST',

packages/backend/src/routes/management/config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
McpServerConfigSchema,
99
} from '../../config';
1010
import { ConfigService } from '../../services/config-service';
11-
import { adminKeyFromYaml } from '../../index';
1211

1312
export async function registerConfigRoutes(fastify: FastifyInstance) {
1413
const configService = ConfigService.getInstance();
@@ -17,8 +16,10 @@ export async function registerConfigRoutes(fastify: FastifyInstance) {
1716

1817
fastify.get('/v0/management/config/status', async (_request, reply) => {
1918
try {
19+
// Check if ADMIN_KEY was loaded from YAML (deprecated, but kept for backward compatibility)
20+
const adminKeyFromYaml = process.env.ADMIN_KEY_FROM_YAML === 'true';
2021
return reply.send({
21-
adminKeyFromYaml: !!adminKeyFromYaml,
22+
adminKeyFromYaml: adminKeyFromYaml,
2223
});
2324
} catch (e: any) {
2425
return reply.code(500).send({ error: 'Internal server error' });

packages/backend/src/services/quota/checkers/__tests__/wisdomgate-checker.test.ts

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import type { QuotaCheckerConfig } from '../../../../types/quota';
33
import { WisdomGateQuotaChecker } from '../wisdomgate-checker';
44
import { QuotaCheckerFactory } from '../../quota-checker-factory';
55

6-
const makeConfig = (apiKey = 'test_api_key'): QuotaCheckerConfig => ({
6+
const makeConfig = (session = 'test_session_token'): QuotaCheckerConfig => ({
77
id: 'wisdomgate-test',
88
provider: 'wisdomgate',
99
type: 'wisdomgate',
1010
enabled: true,
1111
intervalMinutes: 30,
1212
options: {
13-
apiKey,
13+
session,
1414
},
1515
});
1616

@@ -27,22 +27,33 @@ describe('WisdomGateQuotaChecker', () => {
2727
expect(QuotaCheckerFactory.isRegistered('wisdomgate')).toBe(true);
2828
});
2929

30-
it('queries balance with Bearer token and returns subscription quota with dollars', async () => {
30+
it('queries balance with session cookie and returns monthly subscription quota with dollars', async () => {
3131
let capturedUrl: string | undefined;
32-
let capturedAuthorization: string | undefined;
32+
let capturedCookie: string | undefined;
3333

3434
setFetchMock(async (input: RequestInfo | URL, init?: RequestInit) => {
3535
capturedUrl = String(input);
3636
const headers = new Headers(init?.headers);
37-
capturedAuthorization = headers.get('Authorization') ?? undefined;
37+
capturedCookie = headers.get('Cookie') ?? undefined;
3838

3939
return new Response(
4040
JSON.stringify({
41-
available_balance: 23.852028,
42-
package_balance: 23.848028,
43-
cash_balance: 0.004,
44-
token_balance: 23.852028,
45-
is_token_unlimited_quota: false,
41+
object: 'billing_details',
42+
total_usage: 76.147972,
43+
total_available: 23.852028,
44+
regular_amount: 100,
45+
package_details: [
46+
{
47+
package_id: 'pkg_123',
48+
title: 'Test Package',
49+
amount: 23.852028,
50+
total_amount: 100,
51+
expiry_time: 1735689600,
52+
expiry_date: '2025-01-01',
53+
begin_time: 1704067200,
54+
begin_date: '2024-01-01',
55+
},
56+
],
4657
}),
4758
{
4859
status: 200,
@@ -51,22 +62,24 @@ describe('WisdomGateQuotaChecker', () => {
5162
);
5263
});
5364

54-
const checker = new WisdomGateQuotaChecker(makeConfig('my-api-key'));
65+
const checker = new WisdomGateQuotaChecker(makeConfig('my-session-token'));
5566
const result = await checker.checkQuota();
5667

57-
expect(capturedUrl).toBe('https://wisdom-gate.juheapi.com/v1/users/me/balance');
58-
expect(capturedAuthorization).toBe('Bearer my-api-key');
68+
expect(capturedUrl).toBe('https://wisgate.ai/api/dashboard/billing/usage/details');
69+
expect(capturedCookie).toBe('session=my-session-token');
5970

6071
expect(result.success).toBe(true);
6172
expect(result.error).toBeUndefined();
6273
expect(result.windows).toHaveLength(1);
6374

6475
const window = result.windows?.[0];
65-
expect(window?.windowType).toBe('subscription');
76+
expect(window?.windowType).toBe('monthly');
6677
expect(window?.unit).toBe('dollars');
78+
expect(window?.limit).toBe(100);
79+
expect(window?.used).toBeCloseTo(76.147972, 6);
6780
expect(window?.remaining).toBeCloseTo(23.852028, 6);
68-
expect(window?.description).toBe('Wisdom Gate account balance');
69-
expect(window?.resetsAt).toBeUndefined();
81+
expect(window?.description).toBe('Wisdom Gate monthly subscription');
82+
expect(window?.resetsAt).toBeInstanceOf(Date);
7083
});
7184

7285
it('returns error for non-200 response', async () => {
@@ -88,11 +101,22 @@ describe('WisdomGateQuotaChecker', () => {
88101
capturedUrl = String(input);
89102
return new Response(
90103
JSON.stringify({
91-
available_balance: 10,
92-
package_balance: 10,
93-
cash_balance: 0,
94-
token_balance: 10,
95-
is_token_unlimited_quota: false,
104+
object: 'billing_details',
105+
total_usage: 0,
106+
total_available: 10,
107+
regular_amount: 10,
108+
package_details: [
109+
{
110+
package_id: 'pkg_123',
111+
title: 'Test Package',
112+
amount: 10,
113+
total_amount: 10,
114+
expiry_time: 1735689600,
115+
expiry_date: '2025-01-01',
116+
begin_time: 1704067200,
117+
begin_date: '2024-01-01',
118+
},
119+
],
96120
}),
97121
{ status: 200, headers: { 'Content-Type': 'application/json' } }
98122
);
@@ -105,16 +129,18 @@ describe('WisdomGateQuotaChecker', () => {
105129
enabled: true,
106130
intervalMinutes: 30,
107131
options: {
108-
apiKey: 'test-key',
109-
endpoint: 'https://custom.endpoint.example.com/v1/users/me/balance',
132+
session: 'test-session',
133+
endpoint: 'https://custom.endpoint.example.com/api/dashboard/billing/usage/details',
110134
},
111135
});
112136

113137
await checker.checkQuota();
114-
expect(capturedUrl).toBe('https://custom.endpoint.example.com/v1/users/me/balance');
138+
expect(capturedUrl).toBe(
139+
'https://custom.endpoint.example.com/api/dashboard/billing/usage/details'
140+
);
115141
});
116142

117-
it('throws error when apiKey option is missing', async () => {
143+
it('throws error when session option is missing', async () => {
118144
const checker = new WisdomGateQuotaChecker({
119145
id: 'wisdomgate-test',
120146
provider: 'wisdomgate',

packages/backend/test/quota-enforcer.test.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
1+
import { describe, test, expect, beforeEach, afterEach, beforeAll, mock } from 'bun:test';
22
import { QuotaEnforcer, QuotaCheckResult } from '../src/services/quota/quota-enforcer';
33
import { setConfigForTesting, PlexusConfig } from '../src/config';
44
import { getDatabase } from '../src/db/client';
5+
import { runMigrations } from '../src/db/migrate';
56
import * as sqliteSchema from '../drizzle/schema/sqlite';
67
import { eq } from 'drizzle-orm';
78

@@ -33,10 +34,22 @@ describe('QuotaEnforcer', () => {
3334
let quotaEnforcer: QuotaEnforcer;
3435
let db: ReturnType<typeof getDatabase>;
3536

37+
beforeAll(async () => {
38+
// Ensure migrations are run before tests
39+
await runMigrations();
40+
});
41+
3642
beforeEach(async () => {
3743
// Reset database state
3844
db = getDatabase();
39-
await db.delete(sqliteSchema.quotaState);
45+
try {
46+
await db.delete(sqliteSchema.quotaState);
47+
} catch (e: any) {
48+
// Table might not exist yet, ignore
49+
if (!e.message?.includes('no such table')) {
50+
throw e;
51+
}
52+
}
4053

4154
// Reset config
4255
setConfigForTesting(createTestConfig());
@@ -47,7 +60,14 @@ describe('QuotaEnforcer', () => {
4760

4861
afterEach(async () => {
4962
// Clean up test data
50-
await db.delete(sqliteSchema.quotaState);
63+
try {
64+
await db.delete(sqliteSchema.quotaState);
65+
} catch (e: any) {
66+
// Table might not exist, ignore
67+
if (!e.message?.includes('no such table')) {
68+
throw e;
69+
}
70+
}
5171
});
5272

5373
describe('checkQuota', () => {

0 commit comments

Comments
 (0)