test/billing-userstatus-decode.test.js
Rama master · réplica de lectura del repositorio original.
/**
* Test suite for GetUserStatusResponse billing decode (decodeUserStatusFull).
*
* Wire spec (calibrated 2026-07-07, live Teams paid account):
* #1.13.16 = balance (varint, micro-dollar)
* #1.13.17 = billing period start (varint, epoch seconds)
* #1.13.18 = billing period end (varint, epoch seconds)
* #1.13.1.2 = plan name (string)
* #1.13.1.21 (repeated) = credit rate table (f32 fixed32)
*
* Tests: synthetic protobuf buffers (built with proto.js writers) asserting:
* - Balance + period decode correctly (units verified)
* - Rate table decodes (f32 readFloatLE)
* - Catalog pairing (rate indices → selectors)
* - Graceful degradation (missing fields → null, no throw)
*/
import { strict as assert } from 'node:assert';
import { test } from 'node:test';
import {
writeMessageField,
writeVarintField,
writeStringField,
writeFixed32Field,
} from '../src/proto.js';
import { decodeUserStatusFull } from '../src/devin-connect-catalog.js';
/** Helper: build a credit-rate entry (fixed32 f32) */
function buildRateEntry(credit) {
const buf = Buffer.allocUnsafe(4);
buf.writeFloatLE(credit, 0);
return writeMessageField(21, writeFixed32Field(2, buf));
}
/** Helper: build #1.13.1 (plan detail) */
function buildPlanDetail({ name, rates = [] }) {
const planFields = [
writeStringField(2, name),
...rates.map(buildRateEntry),
];
return writeMessageField(1, Buffer.concat(planFields));
}
/** Helper: build #1.13 (billing block) */
function buildBillingBlock({ balance, periodStart, periodEnd, plan }) {
const billingFields = [];
if (plan) billingFields.push(buildPlanDetail(plan));
if (balance != null) billingFields.push(writeVarintField(16, balance));
if (periodStart != null) billingFields.push(writeVarintField(17, periodStart));
if (periodEnd != null) billingFields.push(writeVarintField(18, periodEnd));
return writeMessageField(13, Buffer.concat(billingFields));
}
/** Helper: build full GetUserStatusResponse */
function buildUserStatusResponse({ billing }) {
const field1 = writeMessageField(1, billing);
return field1;
}
test('decodeUserStatusFull: paid account with full billing ledger', () => {
const periodStart = Math.floor(Date.now() / 1000);
const periodEnd = periodStart + 86400 * 5; // 5-day period
const balance = 80_000_000; // $80 in micro-dollars
const billing = buildBillingBlock({
balance,
periodStart,
periodEnd,
plan: {
name: 'Teams',
rates: [25, 50, 6, 1.5, 12],
},
});
const response = buildUserStatusResponse({ billing });
const result = decodeUserStatusFull(response);
assert.equal(result.plan, 'teams');
assert.equal(result.isPaid, true);
assert.equal(result.balance, 80); // 80000000 micro-usd / 1e6 = $80
assert.equal(result.balanceUnit, 'usd'); // already converted to USD
assert.equal(result.balanceMicro, 80000000); // raw micro-usd preserved
assert.equal(result.periodStart, periodStart * 1000); // epoch ms (number)
assert.equal(result.periodEnd, periodEnd * 1000);
assert.ok(Array.isArray(result.rateTable));
assert.equal(result.rateTable.length, 5);
assert.equal(result.rateTable[0], 25);
assert.equal(result.rateTable[1], 50);
assert.equal(result.rateTable[2], 6);
assert.equal(result.rateTable[3], 1.5);
assert.equal(result.rateTable[4], 12);
});
test('decodeUserStatusFull: catalog pairing (rates → selectors)', () => {
const billing = buildBillingBlock({
balance: 100_000_000,
periodStart: 1783411200,
periodEnd: 1783843200,
plan: {
name: 'Teams',
rates: [25, 50, 6],
},
});
const response = buildUserStatusResponse({ billing });
const catalog = [
{ selector: 'claude-opus-4-8-medium' },
{ selector: 'claude-5-fable-medium' },
{ selector: 'claude-sonnet-5-medium' },
];
const result = decodeUserStatusFull(response, catalog);
assert.equal(typeof result.rateTable, 'object');
assert.equal(result.rateTable['claude-opus-4-8-medium'], 25);
assert.equal(result.rateTable['claude-5-fable-medium'], 50);
assert.equal(result.rateTable['claude-sonnet-5-medium'], 6);
});
test('decodeUserStatusFull: free account (minimal billing)', () => {
const billing = buildBillingBlock({
plan: { name: 'Free', rates: [] },
});
const response = buildUserStatusResponse({ billing });
const result = decodeUserStatusFull(response);
assert.equal(result.plan, 'free');
assert.equal(result.isPaid, false);
assert.equal(result.balance, null);
assert.equal(result.balanceUnit, null);
assert.equal(result.periodStart, null);
assert.equal(result.periodEnd, null);
assert.equal(result.rateTable, null);
});
test('decodeUserStatusFull: partial billing (balance only)', () => {
const billing = buildBillingBlock({
balance: 50_000_000,
plan: { name: 'Pro', rates: [] },
});
const response = buildUserStatusResponse({ billing });
const result = decodeUserStatusFull(response);
assert.equal(result.plan, 'pro');
assert.equal(result.isPaid, true);
assert.equal(result.balance, 50);
assert.equal(result.balanceUnit, 'usd');
assert.equal(result.periodStart, null);
assert.equal(result.periodEnd, null);
});
test('decodeUserStatusFull: graceful degrade on malformed rate entry', () => {
// Build a rate table with one valid entry and one malformed (wrong field #99 instead of #2)
const buf = Buffer.allocUnsafe(4);
buf.writeFloatLE(999, 0);
const malformedRate = writeMessageField(21, writeFixed32Field(99, buf)); // field #99, not #2
const planFields = [
writeStringField(2, 'Teams'),
buildRateEntry(25), // valid
malformedRate, // malformed: wrong field number
buildRateEntry(50), // another valid
];
const billing = writeMessageField(13, writeMessageField(1, Buffer.concat(planFields)));
const response = buildUserStatusResponse({ billing });
const result = decodeUserStatusFull(response);
assert.equal(result.plan, 'teams');
assert.ok(Array.isArray(result.rateTable));
assert.equal(result.rateTable.length, 3);
assert.equal(result.rateTable[0], 25);
assert.equal(result.rateTable[1], null); // malformed → null (no field #2)
assert.equal(result.rateTable[2], 50);
});
test('decodeUserStatusFull: empty response (no #1 field)', () => {
const response = Buffer.alloc(0);
const result = decodeUserStatusFull(response);
assert.equal(result.plan, 'unknown');
assert.equal(result.isPaid, false);
assert.equal(result.balance, null);
assert.equal(result.periodStart, null);
assert.equal(result.periodEnd, null);
assert.equal(result.rateTable, null);
});
test('decodeUserStatusFull: fallback plan name from #2.#2 (legacy path)', () => {
// Build a response with plan name only in #2.#2 (no billing block in #1)
// The #2 field is at the TOP level, not inside #1
const field2Inner = writeStringField(2, 'Enterprise');
const field2 = writeMessageField(2, field2Inner);
const result = decodeUserStatusFull(field2);
assert.equal(result.plan, 'enterprise');
assert.equal(result.isPaid, true);
assert.equal(result.balance, null); // no billing block
});
test('decodeUserStatusFull: rate table longer than catalog (excess ignored)', () => {
const billing = buildBillingBlock({
plan: {
name: 'Teams',
rates: [25, 50, 6, 1.5, 12],
},
});
const response = buildUserStatusResponse({ billing });
const catalog = [
{ selector: 'claude-opus-4-8-medium' },
{ selector: 'claude-5-fable-medium' },
// only 2 models vs 5 rates
];
const result = decodeUserStatusFull(response, catalog);
assert.equal(Object.keys(result.rateTable).length, 2);
assert.equal(result.rateTable['claude-opus-4-8-medium'], 25);
assert.equal(result.rateTable['claude-5-fable-medium'], 50);
// rates[2..4] are not paired (no catalog entries)
});
test('decodeUserStatusFull: catalog entry without selector (skipped in pairing)', () => {
const billing = buildBillingBlock({
plan: { name: 'Teams', rates: [25, 50, 6] },
});
const response = buildUserStatusResponse({ billing });
const catalog = [
{ selector: 'claude-opus-4-8-medium' },
{ selector: null }, // malformed catalog entry
{ selector: 'claude-sonnet-5-medium' },
];
const result = decodeUserStatusFull(response, catalog);
assert.equal(Object.keys(result.rateTable).length, 2);
assert.equal(result.rateTable['claude-opus-4-8-medium'], 25);
assert.equal(result.rateTable['claude-sonnet-5-medium'], 6);
// index 1 skipped (no selector)
});
test('decodeUserStatusFull: uint64 -1 balance is unknown, not $18,446,744,073,709.43', () => {
// #1.13.16 is a uint64 on the wire; -1 (the common "unset/unlimited" sentinel)
// arrives as 2^64-1. It must never be formatted as a real on-demand balance.
const billing = buildBillingBlock({ balance: 18446744073709551615n });
const result = decodeUserStatusFull(buildUserStatusResponse({ billing }));
assert.equal(result.balance, null);
assert.equal(result.balanceUnit, null);
assert.equal(result.balanceMicro, undefined);
});
test('decodeUserStatusFull: a plausible balance still decodes after the sentinel guard', () => {
const billing = buildBillingBlock({ balance: 1_500_000 }); // $1.50
const result = decodeUserStatusFull(buildUserStatusResponse({ billing }));
assert.equal(result.balance, 1.5);
assert.equal(result.balanceMicro, 1_500_000);
});