@wopr-network/platform-ui-core 1.22.4 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -3,6 +3,18 @@ import userEvent from "@testing-library/user-event";
|
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { CreateInstanceClient } from "../app/instances/new/create-instance-client";
|
|
5
5
|
|
|
6
|
+
vi.mock("next/navigation", () => ({
|
|
7
|
+
useRouter: vi.fn().mockReturnValue({
|
|
8
|
+
push: vi.fn(),
|
|
9
|
+
replace: vi.fn(),
|
|
10
|
+
prefetch: vi.fn(),
|
|
11
|
+
back: vi.fn(),
|
|
12
|
+
refresh: vi.fn(),
|
|
13
|
+
}),
|
|
14
|
+
usePathname: vi.fn().mockReturnValue("/instances/new"),
|
|
15
|
+
useSearchParams: vi.fn().mockReturnValue(new URLSearchParams()),
|
|
16
|
+
}));
|
|
17
|
+
|
|
6
18
|
vi.mock("@/lib/marketplace-data", () => ({
|
|
7
19
|
listMarketplacePlugins: vi.fn(),
|
|
8
20
|
}));
|
|
@@ -2,25 +2,19 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
vi.mock("@/lib/trpc", () => ({
|
|
4
4
|
trpcVanilla: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
query: vi.fn().mockResolvedValue({
|
|
8
|
-
orgId: "org-1",
|
|
9
|
-
balanceCents: 5000,
|
|
10
|
-
dailyBurnCents: 100,
|
|
11
|
-
runwayDays: 50,
|
|
12
|
-
}),
|
|
13
|
-
},
|
|
14
|
-
orgMemberUsage: {
|
|
5
|
+
billing: {
|
|
6
|
+
creditsBalance: {
|
|
15
7
|
query: vi.fn().mockResolvedValue({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
balance_credits: 5000,
|
|
9
|
+
daily_burn_credits: 100,
|
|
10
|
+
runway_days: 50,
|
|
19
11
|
}),
|
|
20
12
|
},
|
|
21
|
-
|
|
13
|
+
billingInfo: {
|
|
22
14
|
query: vi.fn().mockResolvedValue({ paymentMethods: [], invoices: [] }),
|
|
23
15
|
},
|
|
16
|
+
},
|
|
17
|
+
org: {
|
|
24
18
|
orgTopupCheckout: {
|
|
25
19
|
mutate: vi.fn().mockResolvedValue({
|
|
26
20
|
url: "https://checkout.stripe.com/test",
|
|
@@ -42,7 +36,7 @@ import {
|
|
|
42
36
|
} from "@/lib/org-billing-api";
|
|
43
37
|
|
|
44
38
|
describe("org-billing-api", () => {
|
|
45
|
-
it("getOrgCreditBalance converts
|
|
39
|
+
it("getOrgCreditBalance converts credits to dollars", async () => {
|
|
46
40
|
const result = await getOrgCreditBalance("org-1");
|
|
47
41
|
expect(result.balance).toBe(50);
|
|
48
42
|
expect(result.dailyBurn).toBe(1);
|
|
@@ -9,10 +9,11 @@ interface MockMutate {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface MockTrpcVanilla {
|
|
12
|
+
billing: {
|
|
13
|
+
creditsBalance: MockQuery;
|
|
14
|
+
billingInfo: MockQuery;
|
|
15
|
+
};
|
|
12
16
|
org: {
|
|
13
|
-
orgBillingBalance: MockQuery;
|
|
14
|
-
orgMemberUsage: MockQuery;
|
|
15
|
-
orgBillingInfo: MockQuery;
|
|
16
17
|
orgTopupCheckout: MockMutate;
|
|
17
18
|
orgRemovePaymentMethod: MockMutate;
|
|
18
19
|
};
|
|
@@ -20,10 +21,11 @@ interface MockTrpcVanilla {
|
|
|
20
21
|
|
|
21
22
|
vi.mock("@/lib/trpc", () => ({
|
|
22
23
|
trpcVanilla: {
|
|
24
|
+
billing: {
|
|
25
|
+
creditsBalance: { query: vi.fn() },
|
|
26
|
+
billingInfo: { query: vi.fn() },
|
|
27
|
+
},
|
|
23
28
|
org: {
|
|
24
|
-
orgBillingBalance: { query: vi.fn() },
|
|
25
|
-
orgMemberUsage: { query: vi.fn() },
|
|
26
|
-
orgBillingInfo: { query: vi.fn() },
|
|
27
29
|
orgTopupCheckout: { mutate: vi.fn() },
|
|
28
30
|
orgRemovePaymentMethod: { mutate: vi.fn() },
|
|
29
31
|
},
|
|
@@ -33,8 +35,8 @@ vi.mock("@/lib/trpc", () => ({
|
|
|
33
35
|
describe("org-billing-api null guards", () => {
|
|
34
36
|
it("getOrgCreditBalance handles empty response", async () => {
|
|
35
37
|
const { trpcVanilla } = await import("@/lib/trpc");
|
|
36
|
-
const {
|
|
37
|
-
|
|
38
|
+
const { billing } = trpcVanilla as unknown as MockTrpcVanilla;
|
|
39
|
+
billing.creditsBalance.query.mockResolvedValue({});
|
|
38
40
|
|
|
39
41
|
const { getOrgCreditBalance } = await import("@/lib/org-billing-api");
|
|
40
42
|
const result = await getOrgCreditBalance("org-1");
|
|
@@ -43,11 +45,7 @@ describe("org-billing-api null guards", () => {
|
|
|
43
45
|
expect(result.runway).toBeNull();
|
|
44
46
|
});
|
|
45
47
|
|
|
46
|
-
it("getOrgMemberUsage
|
|
47
|
-
const { trpcVanilla } = await import("@/lib/trpc");
|
|
48
|
-
const { org } = trpcVanilla as unknown as MockTrpcVanilla;
|
|
49
|
-
org.orgMemberUsage.query.mockResolvedValue({ orgId: "o", periodStart: "2026-01-01" });
|
|
50
|
-
|
|
48
|
+
it("getOrgMemberUsage returns stub with empty members", async () => {
|
|
51
49
|
const { getOrgMemberUsage } = await import("@/lib/org-billing-api");
|
|
52
50
|
const result = await getOrgMemberUsage("org-1");
|
|
53
51
|
expect(result.members).toEqual([]);
|
|
@@ -55,8 +53,8 @@ describe("org-billing-api null guards", () => {
|
|
|
55
53
|
|
|
56
54
|
it("getOrgBillingInfo handles empty response", async () => {
|
|
57
55
|
const { trpcVanilla } = await import("@/lib/trpc");
|
|
58
|
-
const {
|
|
59
|
-
|
|
56
|
+
const { billing } = trpcVanilla as unknown as MockTrpcVanilla;
|
|
57
|
+
billing.billingInfo.query.mockResolvedValue({});
|
|
60
58
|
|
|
61
59
|
const { getOrgBillingInfo } = await import("@/lib/org-billing-api");
|
|
62
60
|
const result = await getOrgBillingInfo("org-1");
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CreditCard,
|
|
5
|
+
GitBranch,
|
|
6
|
+
LayoutDashboard,
|
|
7
|
+
LayoutGrid,
|
|
8
|
+
LogOutIcon,
|
|
9
|
+
MessageCircle,
|
|
10
|
+
MessageSquare,
|
|
11
|
+
Network,
|
|
12
|
+
Puzzle,
|
|
13
|
+
Server,
|
|
14
|
+
SettingsIcon,
|
|
15
|
+
Shield,
|
|
16
|
+
Store,
|
|
17
|
+
UserIcon,
|
|
18
|
+
Wallet,
|
|
19
|
+
} from "lucide-react";
|
|
4
20
|
import Image from "next/image";
|
|
5
21
|
import Link from "next/link";
|
|
6
22
|
import { usePathname, useRouter } from "next/navigation";
|
|
@@ -53,6 +69,23 @@ function getInitials(name: string): string {
|
|
|
53
69
|
.toUpperCase();
|
|
54
70
|
}
|
|
55
71
|
|
|
72
|
+
function getNavIcon(href: string) {
|
|
73
|
+
if (href === "/dashboard") return LayoutDashboard;
|
|
74
|
+
if (href === "/chat") return MessageCircle;
|
|
75
|
+
if (href === "/marketplace") return Store;
|
|
76
|
+
if (href.startsWith("/channels")) return MessageSquare;
|
|
77
|
+
if (href.startsWith("/plugins")) return Puzzle;
|
|
78
|
+
if (href.startsWith("/instances")) return LayoutGrid;
|
|
79
|
+
if (href.startsWith("/changesets")) return GitBranch;
|
|
80
|
+
if (href.startsWith("/fleet")) return Server;
|
|
81
|
+
if (href.startsWith("/network")) return Network;
|
|
82
|
+
if (href.startsWith("/billing/credits")) return Wallet;
|
|
83
|
+
if (href.startsWith("/billing")) return CreditCard;
|
|
84
|
+
if (href.startsWith("/settings")) return SettingsIcon;
|
|
85
|
+
if (href.startsWith("/admin")) return Shield;
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
57
90
|
const pathname = usePathname();
|
|
58
91
|
const router = useRouter();
|
|
@@ -86,7 +119,10 @@ export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
86
119
|
return (
|
|
87
120
|
<div className="flex h-full flex-col">
|
|
88
121
|
<div className="flex h-14 items-center border-b border-sidebar-border px-6">
|
|
89
|
-
<span
|
|
122
|
+
<span
|
|
123
|
+
className="text-lg font-semibold tracking-tight text-terminal"
|
|
124
|
+
style={{ textShadow: "0 0 12px var(--terminal-glow, rgba(0, 255, 65, 0.4))" }}
|
|
125
|
+
>
|
|
90
126
|
{productName()}
|
|
91
127
|
</span>
|
|
92
128
|
</div>
|
|
@@ -98,27 +134,33 @@ export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
98
134
|
!item.href.startsWith("/admin") ||
|
|
99
135
|
(user as { role?: string } | undefined)?.role === "platform_admin",
|
|
100
136
|
)
|
|
101
|
-
.map((item) =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
<span className=
|
|
117
|
-
{
|
|
137
|
+
.map((item) => {
|
|
138
|
+
const NavIcon = getNavIcon(item.href);
|
|
139
|
+
return (
|
|
140
|
+
<Link
|
|
141
|
+
key={item.href}
|
|
142
|
+
href={item.href}
|
|
143
|
+
prefetch={false}
|
|
144
|
+
onClick={onNavigate}
|
|
145
|
+
className={cn(
|
|
146
|
+
"flex items-center justify-between rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-foreground",
|
|
147
|
+
isNavActive(item.href, pathname)
|
|
148
|
+
? "bg-terminal/5 border-l-2 border-terminal text-terminal"
|
|
149
|
+
: "text-muted-foreground",
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<span className="flex items-center gap-2.5">
|
|
153
|
+
{NavIcon && <NavIcon className="size-4 shrink-0 opacity-70" />}
|
|
154
|
+
{item.label}
|
|
118
155
|
</span>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
156
|
+
{item.label === "Credits" && creditBalance !== null && (
|
|
157
|
+
<span className={cn("text-xs font-mono", balanceColorClass(creditBalance))}>
|
|
158
|
+
{formatCreditStandard(creditBalance)}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
</Link>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
122
164
|
</nav>
|
|
123
165
|
<div className="border-t border-sidebar-border px-3 py-3">
|
|
124
166
|
{isPending ? (
|
|
@@ -138,7 +180,7 @@ export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
138
180
|
className="size-8 rounded-full object-cover"
|
|
139
181
|
/>
|
|
140
182
|
) : (
|
|
141
|
-
<span className="flex size-8 items-center justify-center rounded-full bg-sidebar-accent text-xs font-semibold">
|
|
183
|
+
<span className="flex size-8 items-center justify-center rounded-full bg-sidebar-accent text-xs font-semibold ring-1 ring-terminal/20">
|
|
142
184
|
{user.name?.trim() ? getInitials(user.name) : <UserIcon className="size-4" />}
|
|
143
185
|
</span>
|
|
144
186
|
)}
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
mockOrgBillingInfoQuery,
|
|
4
|
+
mockCreditsBalanceQuery,
|
|
5
|
+
mockBillingInfoQuery,
|
|
7
6
|
mockOrgTopupCheckoutMutate,
|
|
8
7
|
mockOrgSetDefaultPaymentMethodMutate,
|
|
9
8
|
} = vi.hoisted(() => ({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
mockOrgBillingInfoQuery: vi.fn(),
|
|
9
|
+
mockCreditsBalanceQuery: vi.fn(),
|
|
10
|
+
mockBillingInfoQuery: vi.fn(),
|
|
13
11
|
mockOrgTopupCheckoutMutate: vi.fn(),
|
|
14
12
|
mockOrgSetDefaultPaymentMethodMutate: vi.fn(),
|
|
15
13
|
}));
|
|
16
14
|
|
|
17
15
|
vi.mock("@/lib/trpc", () => ({
|
|
18
16
|
trpcVanilla: {
|
|
17
|
+
billing: {
|
|
18
|
+
creditsBalance: { query: mockCreditsBalanceQuery },
|
|
19
|
+
billingInfo: { query: mockBillingInfoQuery },
|
|
20
|
+
},
|
|
19
21
|
org: {
|
|
20
|
-
orgBillingBalance: { query: mockOrgBillingBalanceQuery },
|
|
21
|
-
orgMemberUsage: { query: mockOrgMemberUsageQuery },
|
|
22
|
-
orgBillingInfo: { query: mockOrgBillingInfoQuery },
|
|
23
22
|
orgTopupCheckout: { mutate: mockOrgTopupCheckoutMutate },
|
|
24
23
|
orgSetDefaultPaymentMethod: { mutate: mockOrgSetDefaultPaymentMethodMutate },
|
|
25
24
|
},
|
|
@@ -38,11 +37,11 @@ import {
|
|
|
38
37
|
describe("getOrgCreditBalance", () => {
|
|
39
38
|
afterEach(() => vi.clearAllMocks());
|
|
40
39
|
|
|
41
|
-
it("converts
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
it("converts credits to dollars and returns balance", async () => {
|
|
41
|
+
mockCreditsBalanceQuery.mockResolvedValue({
|
|
42
|
+
balance_credits: 5000,
|
|
43
|
+
daily_burn_credits: 200,
|
|
44
|
+
runway_days: 25,
|
|
46
45
|
});
|
|
47
46
|
|
|
48
47
|
const result = await getOrgCreditBalance("org-1");
|
|
@@ -51,14 +50,29 @@ describe("getOrgCreditBalance", () => {
|
|
|
51
50
|
dailyBurn: 2,
|
|
52
51
|
runway: 25,
|
|
53
52
|
});
|
|
54
|
-
expect(
|
|
53
|
+
expect(mockCreditsBalanceQuery).toHaveBeenCalledWith({});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("falls back to legacy cents fields", async () => {
|
|
57
|
+
mockCreditsBalanceQuery.mockResolvedValue({
|
|
58
|
+
balance_cents: 3000,
|
|
59
|
+
daily_burn_cents: 100,
|
|
60
|
+
runway_days: 30,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await getOrgCreditBalance("org-1");
|
|
64
|
+
expect(result).toEqual({
|
|
65
|
+
balance: 30,
|
|
66
|
+
dailyBurn: 1,
|
|
67
|
+
runway: 30,
|
|
68
|
+
});
|
|
55
69
|
});
|
|
56
70
|
|
|
57
71
|
it("defaults to zero balance when response fields are null", async () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
mockCreditsBalanceQuery.mockResolvedValue({
|
|
73
|
+
balance_credits: null,
|
|
74
|
+
daily_burn_credits: null,
|
|
75
|
+
runway_days: null,
|
|
62
76
|
});
|
|
63
77
|
|
|
64
78
|
const result = await getOrgCreditBalance("org-1");
|
|
@@ -70,7 +84,7 @@ describe("getOrgCreditBalance", () => {
|
|
|
70
84
|
});
|
|
71
85
|
|
|
72
86
|
it("defaults to zero balance when response fields are undefined", async () => {
|
|
73
|
-
|
|
87
|
+
mockCreditsBalanceQuery.mockResolvedValue({});
|
|
74
88
|
|
|
75
89
|
const result = await getOrgCreditBalance("org-1");
|
|
76
90
|
expect(result).toEqual({
|
|
@@ -81,101 +95,17 @@ describe("getOrgCreditBalance", () => {
|
|
|
81
95
|
});
|
|
82
96
|
|
|
83
97
|
it("propagates tRPC errors", async () => {
|
|
84
|
-
|
|
98
|
+
mockCreditsBalanceQuery.mockRejectedValue(new Error("Forbidden"));
|
|
85
99
|
await expect(getOrgCreditBalance("org-1")).rejects.toThrow("Forbidden");
|
|
86
100
|
});
|
|
87
101
|
});
|
|
88
102
|
|
|
89
103
|
describe("getOrgMemberUsage", () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
it("transforms member usage with cents-to-dollars conversion", async () => {
|
|
93
|
-
mockOrgMemberUsageQuery.mockResolvedValue({
|
|
94
|
-
orgId: "org-1",
|
|
95
|
-
periodStart: "2026-03-01",
|
|
96
|
-
members: [
|
|
97
|
-
{
|
|
98
|
-
memberId: "m-1",
|
|
99
|
-
name: "Alice",
|
|
100
|
-
email: "alice@test.com",
|
|
101
|
-
creditsConsumedCents: 1500,
|
|
102
|
-
lastActiveAt: "2026-03-02T10:00:00Z",
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
memberId: "m-2",
|
|
106
|
-
name: "Bob",
|
|
107
|
-
email: "bob@test.com",
|
|
108
|
-
creditsConsumedCents: 300,
|
|
109
|
-
lastActiveAt: null,
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const result = await getOrgMemberUsage("org-1");
|
|
115
|
-
expect(result).toEqual({
|
|
116
|
-
orgId: "org-1",
|
|
117
|
-
periodStart: "2026-03-01",
|
|
118
|
-
members: [
|
|
119
|
-
{
|
|
120
|
-
memberId: "m-1",
|
|
121
|
-
name: "Alice",
|
|
122
|
-
email: "alice@test.com",
|
|
123
|
-
creditsConsumed: 15,
|
|
124
|
-
lastActiveAt: "2026-03-02T10:00:00Z",
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
memberId: "m-2",
|
|
128
|
-
name: "Bob",
|
|
129
|
-
email: "bob@test.com",
|
|
130
|
-
creditsConsumed: 3,
|
|
131
|
-
lastActiveAt: null,
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
});
|
|
135
|
-
expect(mockOrgMemberUsageQuery).toHaveBeenCalledWith({ orgId: "org-1" });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("defaults member fields when properties are missing", async () => {
|
|
139
|
-
mockOrgMemberUsageQuery.mockResolvedValue({
|
|
140
|
-
orgId: "org-1",
|
|
141
|
-
periodStart: "2026-03-01",
|
|
142
|
-
members: [{}],
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const result = await getOrgMemberUsage("org-1");
|
|
146
|
-
expect(result.members[0]).toEqual({
|
|
147
|
-
memberId: "",
|
|
148
|
-
name: "",
|
|
149
|
-
email: "",
|
|
150
|
-
creditsConsumed: 0,
|
|
151
|
-
lastActiveAt: null,
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("handles null members array", async () => {
|
|
156
|
-
mockOrgMemberUsageQuery.mockResolvedValue({
|
|
157
|
-
orgId: "org-1",
|
|
158
|
-
periodStart: "2026-03-01",
|
|
159
|
-
members: null,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const result = await getOrgMemberUsage("org-1");
|
|
163
|
-
expect(result.members).toEqual([]);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("falls back orgId and periodStart when missing from response", async () => {
|
|
167
|
-
mockOrgMemberUsageQuery.mockResolvedValue({
|
|
168
|
-
members: [],
|
|
169
|
-
});
|
|
170
|
-
|
|
104
|
+
it("returns stub data with empty members array", async () => {
|
|
171
105
|
const result = await getOrgMemberUsage("org-1");
|
|
172
106
|
expect(result.orgId).toBe("org-1");
|
|
173
|
-
expect(result.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
it("propagates tRPC errors", async () => {
|
|
177
|
-
mockOrgMemberUsageQuery.mockRejectedValue(new Error("Server error"));
|
|
178
|
-
await expect(getOrgMemberUsage("org-1")).rejects.toThrow("Server error");
|
|
107
|
+
expect(result.members).toEqual([]);
|
|
108
|
+
expect(result.periodStart).toBeTruthy();
|
|
179
109
|
});
|
|
180
110
|
});
|
|
181
111
|
|
|
@@ -195,7 +125,7 @@ describe("getOrgBillingInfo", () => {
|
|
|
195
125
|
hostedLineItems: undefined,
|
|
196
126
|
},
|
|
197
127
|
];
|
|
198
|
-
|
|
128
|
+
mockBillingInfoQuery.mockResolvedValue({ paymentMethods, invoices });
|
|
199
129
|
|
|
200
130
|
const result = await getOrgBillingInfo("org-1");
|
|
201
131
|
expect(result).toEqual({
|
|
@@ -212,11 +142,11 @@ describe("getOrgBillingInfo", () => {
|
|
|
212
142
|
},
|
|
213
143
|
],
|
|
214
144
|
});
|
|
215
|
-
expect(
|
|
145
|
+
expect(mockBillingInfoQuery).toHaveBeenCalledWith({});
|
|
216
146
|
});
|
|
217
147
|
|
|
218
148
|
it("defaults to empty arrays when fields are null", async () => {
|
|
219
|
-
|
|
149
|
+
mockBillingInfoQuery.mockResolvedValue({
|
|
220
150
|
paymentMethods: null,
|
|
221
151
|
invoices: null,
|
|
222
152
|
});
|
|
@@ -226,15 +156,16 @@ describe("getOrgBillingInfo", () => {
|
|
|
226
156
|
});
|
|
227
157
|
|
|
228
158
|
it("defaults to empty arrays when fields are undefined", async () => {
|
|
229
|
-
|
|
159
|
+
mockBillingInfoQuery.mockResolvedValue({});
|
|
230
160
|
|
|
231
161
|
const result = await getOrgBillingInfo("org-1");
|
|
232
162
|
expect(result).toEqual({ paymentMethods: [], invoices: [] });
|
|
233
163
|
});
|
|
234
164
|
|
|
235
|
-
it("
|
|
236
|
-
|
|
237
|
-
await
|
|
165
|
+
it("returns defaults on tRPC error instead of throwing", async () => {
|
|
166
|
+
mockBillingInfoQuery.mockRejectedValue(new Error("Not found"));
|
|
167
|
+
const result = await getOrgBillingInfo("org-1");
|
|
168
|
+
expect(result).toEqual({ paymentMethods: [], invoices: [] });
|
|
238
169
|
});
|
|
239
170
|
});
|
|
240
171
|
|