Skip to content

Commit 3952a01

Browse files
authored
fix: validate invoice amounts in cli (#500)
1 parent 340795a commit 3952a01

2 files changed

Lines changed: 70 additions & 3 deletions

File tree

cli/src/commands/invoices.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,58 @@ describe("invoices create", () => {
143143
pr_links: [searchUrl, "https://github.com/org/repo/pull/42"],
144144
});
145145
});
146+
147+
it("rejects invalid amount values before posting", async () => {
148+
const { handleError } = await import("../helpers.js");
149+
150+
await run([
151+
"invoices", "create", "gig-123",
152+
"--application-id", "app-456",
153+
"--amount", "not-a-number",
154+
"--payment-currency", "usdc_pol",
155+
"--wallet-address", "0xabc",
156+
]);
157+
158+
expect(mockClient.post).not.toHaveBeenCalled();
159+
expect(handleError).toHaveBeenCalledWith(
160+
expect.objectContaining({ message: expect.stringContaining("Invalid --amount") }),
161+
expect.any(Object)
162+
);
163+
});
164+
165+
it("rejects partially numeric amount values before posting", async () => {
166+
const { handleError } = await import("../helpers.js");
167+
168+
await run([
169+
"invoices", "create", "gig-123",
170+
"--application-id", "app-456",
171+
"--amount", "10usd",
172+
"--payment-currency", "usdc_pol",
173+
"--wallet-address", "0xabc",
174+
]);
175+
176+
expect(mockClient.post).not.toHaveBeenCalled();
177+
expect(handleError).toHaveBeenCalledWith(
178+
expect.objectContaining({ message: expect.stringContaining("Invalid --amount") }),
179+
expect.any(Object)
180+
);
181+
});
182+
183+
it("rejects non-positive line item quantities before posting", async () => {
184+
const { handleError } = await import("../helpers.js");
185+
186+
await run([
187+
"invoices", "create", "gig-123",
188+
"--application-id", "app-456",
189+
"--item", "Bug fix|-2|1",
190+
"--payment-currency", "usdc_pol",
191+
"--wallet-address", "0xabc",
192+
]);
193+
194+
expect(mockClient.post).not.toHaveBeenCalled();
195+
expect(handleError).toHaveBeenCalledWith(
196+
expect.objectContaining({ message: expect.stringContaining("positive quantity") }),
197+
expect.any(Object)
198+
);
199+
});
146200
});

cli/src/commands/invoices.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,19 @@ export function registerInvoicesCommands(program: Command): void {
134134
try {
135135
const items = cmdOpts.item.map((spec) => {
136136
const [description = "", qty = "1", unitPrice = "", link = ""] = spec.split("|");
137+
const quantity = qty.trim() === "" ? 1 : Number(qty);
138+
const unitPriceValue = Number(unitPrice);
137139
const item: Record<string, unknown> = {
138140
description: description.trim(),
139-
quantity: parseFloat(qty) || 1,
140-
unit_price: parseFloat(unitPrice),
141+
quantity: Number.isFinite(quantity) ? quantity : 1,
142+
unit_price: unitPriceValue,
141143
};
142144
if (link.trim()) item.link = link.trim();
145+
if (!Number.isFinite(item.quantity as number) || (item.quantity as number) <= 0) {
146+
throw new Error(
147+
`Invalid --item "${spec}" — expected "description|qty|unit_price[|link]" with a positive quantity`
148+
);
149+
}
143150
if (!Number.isFinite(item.unit_price as number) || (item.unit_price as number) <= 0) {
144151
throw new Error(
145152
`Invalid --item "${spec}" — expected "description|qty|unit_price[|link]" with a positive unit price`
@@ -158,7 +165,13 @@ export function registerInvoicesCommands(program: Command): void {
158165
payment_currency: cmdOpts.paymentCurrency,
159166
merchant_wallet_address: cmdOpts.walletAddress,
160167
};
161-
if (cmdOpts.amount) body.amount = parseFloat(cmdOpts.amount);
168+
if (cmdOpts.amount) {
169+
const amount = Number(cmdOpts.amount);
170+
if (!Number.isFinite(amount) || amount <= 0) {
171+
throw new Error(`Invalid --amount "${cmdOpts.amount}" — expected a positive number`);
172+
}
173+
body.amount = amount;
174+
}
162175
if (items.length > 0) body.items = items;
163176
if (cmdOpts.prLinks) {
164177
const prLinks = cmdOpts.prLinks

0 commit comments

Comments
 (0)