feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
121
apps/mobile-rn/__tests__/stt-quota.local.integration.mjs
Normal file
121
apps/mobile-rn/__tests__/stt-quota.local.integration.mjs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const url = process.env.SUPABASE_URL;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
const publishableKey = process.env.SUPABASE_ANON_KEY ?? process.env.SUPABASE_PUBLISHABLE_KEY;
|
||||
assert(url && serviceKey && publishableKey, 'SUPABASE_URL, service role key and public key are required');
|
||||
|
||||
const service = createClient(url, serviceKey, {
|
||||
auth: { persistSession: false, autoRefreshToken: false },
|
||||
});
|
||||
const email = `stt-quota-${randomUUID()}@example.invalid`;
|
||||
const password = `Fixture-${randomUUID()}!`;
|
||||
let userId = null;
|
||||
let assertions = 0;
|
||||
|
||||
function ok(condition, message) {
|
||||
assert(condition, message);
|
||||
assertions += 1;
|
||||
}
|
||||
|
||||
async function reserve(id) {
|
||||
const { data, error } = await service.rpc('reserve_stt_quota', {
|
||||
p_user_id: userId,
|
||||
p_reservation_id: id,
|
||||
});
|
||||
assert.equal(error, null, error?.message);
|
||||
return data;
|
||||
}
|
||||
|
||||
async function finalize(id, succeeded) {
|
||||
const { data, error } = await service.rpc('finalize_stt_quota', {
|
||||
p_reservation_id: id,
|
||||
p_succeeded: succeeded,
|
||||
});
|
||||
assert.equal(error, null, error?.message);
|
||||
return data;
|
||||
}
|
||||
|
||||
try {
|
||||
const created = await service.auth.admin.createUser({ email, password, email_confirm: true });
|
||||
assert.equal(created.error, null, created.error?.message);
|
||||
userId = created.data.user.id;
|
||||
|
||||
const subscription = await service.from('subscriptions')
|
||||
.update({ tier: 'free', status: 'active', overage_credits: 0 })
|
||||
.eq('user_id', userId);
|
||||
assert.equal(subscription.error, null, subscription.error?.message);
|
||||
const usage = await service.from('daily_usage').insert({
|
||||
user_id: userId,
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
feature: 'stt_transcribe',
|
||||
count: 249,
|
||||
});
|
||||
assert.equal(usage.error, null, usage.error?.message);
|
||||
|
||||
const competingIds = Array.from({ length: 20 }, () => randomUUID());
|
||||
const competing = await Promise.all(competingIds.map(reserve));
|
||||
const winners = competing.filter((item) => item.allowed === true);
|
||||
ok(winners.length === 1, 'twenty concurrent requests reserve exactly one remaining base unit');
|
||||
ok(competing.filter((item) => item.status === 'denied').length === 19, 'all excess work is denied before provider use');
|
||||
await finalize(winners[0].reservation_id, false);
|
||||
|
||||
const replayId = randomUUID();
|
||||
const replay = await Promise.all(Array.from({ length: 10 }, () => reserve(replayId)));
|
||||
ok(replay.every((item) => item.allowed && item.reservation_id === replayId), 'same request replay is idempotent');
|
||||
const usageAfterReplay = await service.from('daily_usage')
|
||||
.select('count')
|
||||
.eq('user_id', userId)
|
||||
.eq('feature', 'stt_transcribe')
|
||||
.single();
|
||||
assert.equal(usageAfterReplay.error, null, usageAfterReplay.error?.message);
|
||||
ok(usageAfterReplay.data.count === 250, 'idempotent replay consumes one unit only');
|
||||
await finalize(replayId, true);
|
||||
|
||||
const creditsUpdate = await service.from('subscriptions')
|
||||
.update({ overage_credits: 3 })
|
||||
.eq('user_id', userId);
|
||||
assert.equal(creditsUpdate.error, null, creditsUpdate.error?.message);
|
||||
const overage = await Promise.all(Array.from({ length: 10 }, () => reserve(randomUUID())));
|
||||
const overageWinners = overage.filter((item) => item.allowed === true);
|
||||
ok(overageWinners.length === 3, 'concurrent requests cannot overspend three overage credits');
|
||||
ok(overageWinners.every((item) => item.consumed_from === 'overage'), 'base exhaustion uses only overage');
|
||||
await finalize(overageWinners[0].reservation_id, true);
|
||||
await Promise.all(overageWinners.slice(1).map((item) => finalize(item.reservation_id, false)));
|
||||
|
||||
const finalSubscription = await service.from('subscriptions')
|
||||
.select('overage_credits')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
const finalUsage = await service.from('daily_usage')
|
||||
.select('count')
|
||||
.eq('user_id', userId)
|
||||
.eq('feature', 'stt_transcribe')
|
||||
.single();
|
||||
assert.equal(finalSubscription.error, null, finalSubscription.error?.message);
|
||||
assert.equal(finalUsage.error, null, finalUsage.error?.message);
|
||||
ok(finalSubscription.data.overage_credits === 2, 'two failed providers refund exactly two overage credits');
|
||||
ok(finalUsage.data.count === 251, 'only the successful overage provider remains charged');
|
||||
|
||||
const userClient = createClient(url, publishableKey, {
|
||||
auth: { persistSession: false, autoRefreshToken: false },
|
||||
});
|
||||
const signedIn = await userClient.auth.signInWithPassword({ email, password });
|
||||
assert.equal(signedIn.error, null, signedIn.error?.message);
|
||||
const forbiddenLedger = await userClient.from('stt_quota_reservations').select('id').limit(1);
|
||||
ok(forbiddenLedger.error !== null, 'authenticated users cannot read the reservation ledger');
|
||||
const forbiddenReserve = await userClient.rpc('reserve_stt_quota', {
|
||||
p_user_id: userId,
|
||||
p_reservation_id: randomUUID(),
|
||||
});
|
||||
ok(forbiddenReserve.error !== null, 'authenticated users cannot invoke reservation RPCs');
|
||||
|
||||
console.log(`stt quota integration GREEN: ${assertions} assertions`);
|
||||
} finally {
|
||||
if (userId) {
|
||||
const removed = await service.auth.admin.deleteUser(userId);
|
||||
assert.equal(removed.error, null, removed.error?.message);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue