Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions cli/src/commands/invoices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,58 @@ describe("invoices create", () => {
pr_links: [searchUrl, "https://github.com/org/repo/pull/42"],
});
});

it("rejects invalid amount values before posting", async () => {
const { handleError } = await import("../helpers.js");

await run([
"invoices", "create", "gig-123",
"--application-id", "app-456",
"--amount", "not-a-number",
"--payment-currency", "usdc_pol",
"--wallet-address", "0xabc",
]);

expect(mockClient.post).not.toHaveBeenCalled();
expect(handleError).toHaveBeenCalledWith(
expect.objectContaining({ message: expect.stringContaining("Invalid --amount") }),
expect.any(Object)
);
});

it("rejects partially numeric amount values before posting", async () => {
const { handleError } = await import("../helpers.js");

await run([
"invoices", "create", "gig-123",
"--application-id", "app-456",
"--amount", "10usd",
"--payment-currency", "usdc_pol",
"--wallet-address", "0xabc",
]);

expect(mockClient.post).not.toHaveBeenCalled();
expect(handleError).toHaveBeenCalledWith(
expect.objectContaining({ message: expect.stringContaining("Invalid --amount") }),
expect.any(Object)
);
});

it("rejects non-positive line item quantities before posting", async () => {
const { handleError } = await import("../helpers.js");

await run([
"invoices", "create", "gig-123",
"--application-id", "app-456",
"--item", "Bug fix|-2|1",
"--payment-currency", "usdc_pol",
"--wallet-address", "0xabc",
]);

expect(mockClient.post).not.toHaveBeenCalled();
expect(handleError).toHaveBeenCalledWith(
expect.objectContaining({ message: expect.stringContaining("positive quantity") }),
expect.any(Object)
);
});
});
19 changes: 16 additions & 3 deletions cli/src/commands/invoices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,19 @@ export function registerInvoicesCommands(program: Command): void {
try {
const items = cmdOpts.item.map((spec) => {
const [description = "", qty = "1", unitPrice = "", link = ""] = spec.split("|");
const quantity = qty.trim() === "" ? 1 : Number(qty);
const unitPriceValue = Number(unitPrice);
const item: Record<string, unknown> = {
description: description.trim(),
quantity: parseFloat(qty) || 1,
unit_price: parseFloat(unitPrice),
quantity: Number.isFinite(quantity) ? quantity : 1,
unit_price: unitPriceValue,
};
if (link.trim()) item.link = link.trim();
if (!Number.isFinite(item.quantity as number) || (item.quantity as number) <= 0) {
throw new Error(
`Invalid --item "${spec}" — expected "description|qty|unit_price[|link]" with a positive quantity`
);
}
if (!Number.isFinite(item.unit_price as number) || (item.unit_price as number) <= 0) {
throw new Error(
`Invalid --item "${spec}" — expected "description|qty|unit_price[|link]" with a positive unit price`
Expand All @@ -158,7 +165,13 @@ export function registerInvoicesCommands(program: Command): void {
payment_currency: cmdOpts.paymentCurrency,
merchant_wallet_address: cmdOpts.walletAddress,
};
if (cmdOpts.amount) body.amount = parseFloat(cmdOpts.amount);
if (cmdOpts.amount) {
const amount = Number(cmdOpts.amount);
if (!Number.isFinite(amount) || amount <= 0) {
throw new Error(`Invalid --amount "${cmdOpts.amount}" — expected a positive number`);
}
body.amount = amount;
}
if (items.length > 0) body.items = items;
if (cmdOpts.prLinks) {
const prLinks = cmdOpts.prLinks
Expand Down
Loading