Skip to content

Commit 0f0c40f

Browse files
committed
fix: tests and fix updateKey err
1 parent 1fe83b4 commit 0f0c40f

File tree

7 files changed

+336
-18
lines changed

7 files changed

+336
-18
lines changed

apps/api/src/routes/v1_keys_deleteKey.happy.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,68 @@ test("hard deletes key", async (t) => {
7777
});
7878
expect(found).toBeUndefined();
7979
});
80+
81+
test("permanent delete removes credits from credits table", async (t) => {
82+
const h = await IntegrationHarness.init(t);
83+
const keyId = newId("test");
84+
const key = new KeyV1({ prefix: "test", byteLength: 16 }).toString();
85+
86+
// Create key
87+
await h.db.primary.insert(schema.keys).values({
88+
id: keyId,
89+
keyAuthId: h.resources.userKeyAuth.id,
90+
hash: await sha256(key),
91+
start: key.slice(0, 8),
92+
workspaceId: h.resources.userWorkspace.id,
93+
createdAtM: Date.now(),
94+
});
95+
96+
// Create credits in new table
97+
const creditId = newId("credit");
98+
await h.db.primary.insert(schema.credits).values({
99+
id: creditId,
100+
keyId,
101+
workspaceId: h.resources.userWorkspace.id,
102+
remaining: 1000,
103+
refillAmount: null,
104+
refillDay: null,
105+
createdAt: Date.now(),
106+
refilledAt: Date.now(),
107+
identityId: null,
108+
updatedAt: null,
109+
});
110+
111+
// Verify credits exist
112+
const creditsBefore = await h.db.primary.query.credits.findFirst({
113+
where: (table, { eq }) => eq(table.keyId, keyId),
114+
});
115+
expect(creditsBefore).toBeDefined();
116+
expect(creditsBefore!.id).toBe(creditId);
117+
118+
const root = await h.createRootKey([`api.${h.resources.userApi.id}.delete_key`]);
119+
const res = await h.post<V1KeysDeleteKeyRequest, V1KeysDeleteKeyResponse>({
120+
url: "/v1/keys.deleteKey",
121+
headers: {
122+
"Content-Type": "application/json",
123+
Authorization: `Bearer ${root.key}`,
124+
},
125+
body: {
126+
keyId,
127+
permanent: true,
128+
},
129+
});
130+
131+
expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200);
132+
133+
// Verify key is deleted
134+
const foundKey = await h.db.primary.query.keys.findFirst({
135+
where: (table, { eq }) => eq(table.id, keyId),
136+
});
137+
expect(foundKey).toBeUndefined();
138+
139+
// Verify credits are also deleted
140+
const creditsAfter = await h.db.primary.query.credits.findFirst({
141+
where: (table, { eq }) => eq(table.keyId, keyId),
142+
});
143+
expect(creditsAfter).toBeUndefined();
144+
});

apps/api/src/routes/v1_keys_getKey.happy.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,124 @@ test("returns identity", async (t) => {
6666
expect(res.body.identity!.id).toEqual(identity.id);
6767
expect(res.body.identity!.externalId).toEqual(identity.externalId);
6868
});
69+
70+
test("returns credits from new credits table", async (t) => {
71+
const h = await IntegrationHarness.init(t);
72+
const root = await h.createRootKey(["api.*.read_key"]);
73+
74+
const keyId = newId("test");
75+
await h.db.primary.insert(schema.keys).values({
76+
id: keyId,
77+
keyAuthId: h.resources.userKeyAuth.id,
78+
workspaceId: h.resources.userWorkspace.id,
79+
start: "test",
80+
hash: await sha256(new KeyV1({ byteLength: 16 }).toString()),
81+
createdAtM: Date.now(),
82+
});
83+
84+
// Create credits in new table with monthly refill
85+
const creditId = newId("credit");
86+
await h.db.primary.insert(schema.credits).values({
87+
id: creditId,
88+
keyId,
89+
workspaceId: h.resources.userWorkspace.id,
90+
remaining: 5000,
91+
refillAmount: 2000,
92+
refillDay: 10, // monthly refill
93+
createdAt: Date.now(),
94+
refilledAt: Date.now(),
95+
identityId: null,
96+
updatedAt: null,
97+
});
98+
99+
const res = await h.get<V1KeysGetKeyResponse>({
100+
url: `/v1/keys.getKey?keyId=${keyId}`,
101+
headers: {
102+
Authorization: `Bearer ${root.key}`,
103+
},
104+
});
105+
106+
expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200);
107+
expect(res.body.remaining).toBe(5000);
108+
expect(res.body.refill).toBeDefined();
109+
expect(res.body.refill!.interval).toBe("monthly");
110+
expect(res.body.refill!.amount).toBe(2000);
111+
expect(res.body.refill!.refillDay).toBe(10);
112+
});
113+
114+
test("returns credits from legacy remaining field when no credits table entry", async (t) => {
115+
const h = await IntegrationHarness.init(t);
116+
const root = await h.createRootKey(["api.*.read_key"]);
117+
118+
const keyId = newId("test");
119+
await h.db.primary.insert(schema.keys).values({
120+
id: keyId,
121+
keyAuthId: h.resources.userKeyAuth.id,
122+
workspaceId: h.resources.userWorkspace.id,
123+
start: "test",
124+
hash: await sha256(new KeyV1({ byteLength: 16 }).toString()),
125+
remaining: 800,
126+
refillAmount: 100,
127+
refillDay: null, // daily refill
128+
createdAtM: Date.now(),
129+
});
130+
131+
const res = await h.get<V1KeysGetKeyResponse>({
132+
url: `/v1/keys.getKey?keyId=${keyId}`,
133+
headers: {
134+
Authorization: `Bearer ${root.key}`,
135+
},
136+
});
137+
138+
expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200);
139+
expect(res.body.remaining).toBe(800);
140+
expect(res.body.refill).toBeDefined();
141+
expect(res.body.refill!.interval).toBe("daily");
142+
expect(res.body.refill!.amount).toBe(100);
143+
});
144+
145+
test("prefers new credits table over legacy remaining field", async (t) => {
146+
const h = await IntegrationHarness.init(t);
147+
const root = await h.createRootKey(["api.*.read_key"]);
148+
149+
const keyId = newId("test");
150+
// Create key with legacy credits (should be ignored)
151+
await h.db.primary.insert(schema.keys).values({
152+
id: keyId,
153+
keyAuthId: h.resources.userKeyAuth.id,
154+
workspaceId: h.resources.userWorkspace.id,
155+
start: "test",
156+
hash: await sha256(new KeyV1({ byteLength: 16 }).toString()),
157+
remaining: 123, // This should be ignored
158+
refillAmount: 50, // This should be ignored
159+
createdAtM: Date.now(),
160+
});
161+
162+
// Create credits in new table (should take precedence)
163+
const creditId = newId("credit");
164+
await h.db.primary.insert(schema.credits).values({
165+
id: creditId,
166+
keyId,
167+
workspaceId: h.resources.userWorkspace.id,
168+
remaining: 7000,
169+
refillAmount: 3000,
170+
refillDay: null, // daily refill
171+
createdAt: Date.now(),
172+
refilledAt: Date.now(),
173+
identityId: null,
174+
updatedAt: null,
175+
});
176+
177+
const res = await h.get<V1KeysGetKeyResponse>({
178+
url: `/v1/keys.getKey?keyId=${keyId}`,
179+
headers: {
180+
Authorization: `Bearer ${root.key}`,
181+
},
182+
});
183+
184+
expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200);
185+
// Should use values from credits table, not legacy fields
186+
expect(res.body.remaining).toBe(7000);
187+
expect(res.body.refill).toBeDefined();
188+
expect(res.body.refill!.amount).toBe(3000);
189+
});

apps/api/src/routes/v1_keys_getKey.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -148,22 +148,18 @@ export const registerV1KeysGetKey = (app: App) =>
148148

149149
const ratelimit = key.ratelimits.find((rl) => rl.name === "default");
150150

151-
let refill = undefined;
152-
if (data.credits?.refillAmount) {
153-
refill = {
154-
interval: data.credits.refillDay ? ("monthly" as const) : ("daily" as const),
155-
amount: data.credits.refillAmount,
156-
refillDay: data.credits.refillDay ?? null,
157-
lastRefillAt: data.credits.refilledAt || undefined,
158-
};
159-
}
151+
// Prefer new credits table over legacy fields
152+
const refillAmount = data.credits?.refillAmount ?? key.refillAmount;
153+
const refillDay = data.credits?.refillDay ?? key.refillDay;
154+
const lastRefillAt = data.credits?.refilledAt ?? key.lastRefillAt?.getTime();
160155

161-
if (key.refillAmount) {
156+
let refill = undefined;
157+
if (refillAmount) {
162158
refill = {
163-
interval: key.refillDay ? ("monthly" as const) : ("daily" as const),
164-
amount: key.refillAmount,
165-
refillDay: key.refillDay ?? null,
166-
lastRefillAt: key.lastRefillAt?.getTime() || undefined,
159+
interval: refillDay ? ("monthly" as const) : ("daily" as const),
160+
amount: refillAmount,
161+
refillDay: refillDay ?? null,
162+
lastRefillAt: lastRefillAt ?? undefined,
167163
};
168164
}
169165

@@ -178,7 +174,7 @@ export const registerV1KeysGetKey = (app: App) =>
178174
createdAt: key.createdAtM,
179175
updatedAt: key.updatedAtM ?? undefined,
180176
expires: key.expires?.getTime() ?? undefined,
181-
remaining: (key.remaining || data.credits?.remaining) ?? undefined,
177+
remaining: data.credits?.remaining ?? key.remaining ?? undefined,
182178
refill: refill,
183179
ratelimit: ratelimit
184180
? {

apps/api/src/routes/v1_keys_updateKey.happy.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,7 @@ test("update ratelimit should not disable it", async (t) => {
10941094
expect(verify.body.ratelimit!.limit).toBe(5);
10951095
expect(verify.body.ratelimit!.remaining).toBe(4);
10961096
});
1097+
10971098
describe("When refillDay is omitted.", () => {
10981099
test("should provide default value", async (t) => {
10991100
const h = await IntegrationHarness.init(t);

apps/api/src/routes/v1_keys_updateKey.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,8 @@ export const registerV1KeysUpdate = (app: App) =>
456456
creditChanges.refilledAt = null;
457457
} else {
458458
creditChanges.refillAmount = req.refill.amount;
459-
creditChanges.refillDay = req.refill.refillDay ?? null;
459+
creditChanges.refillDay =
460+
req.refill.interval === "monthly" ? req.refill.refillDay ?? 1 : null;
460461
}
461462
}
462463

@@ -467,7 +468,7 @@ export const registerV1KeysUpdate = (app: App) =>
467468
changes.lastRefillAt = null;
468469
} else {
469470
changes.refillAmount = req.refill.amount;
470-
changes.refillDay = req.refill.refillDay ?? null;
471+
changes.refillDay = req.refill.interval === "monthly" ? req.refill.refillDay ?? 1 : null;
471472
}
472473
}
473474
}

0 commit comments

Comments
 (0)