@wopr-network/platform-core 1.68.0 → 1.69.0
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/dist/backup/types.d.ts +1 -1
- package/dist/server/__tests__/build-container.test.d.ts +1 -0
- package/dist/server/__tests__/build-container.test.js +339 -0
- package/dist/server/__tests__/container.test.d.ts +1 -0
- package/dist/server/__tests__/container.test.js +170 -0
- package/dist/server/__tests__/lifecycle.test.d.ts +1 -0
- package/dist/server/__tests__/lifecycle.test.js +90 -0
- package/dist/server/__tests__/mount-routes.test.d.ts +1 -0
- package/dist/server/__tests__/mount-routes.test.js +151 -0
- package/dist/server/boot-config.d.ts +51 -0
- package/dist/server/boot-config.js +7 -0
- package/dist/server/container.d.ts +81 -0
- package/dist/server/container.js +134 -0
- package/dist/server/index.d.ts +33 -0
- package/dist/server/index.js +66 -0
- package/dist/server/lifecycle.d.ts +25 -0
- package/dist/server/lifecycle.js +46 -0
- package/dist/server/middleware/__tests__/admin-auth.test.d.ts +1 -0
- package/dist/server/middleware/__tests__/admin-auth.test.js +59 -0
- package/dist/server/middleware/__tests__/tenant-proxy.test.d.ts +1 -0
- package/dist/server/middleware/__tests__/tenant-proxy.test.js +268 -0
- package/dist/server/middleware/admin-auth.d.ts +18 -0
- package/dist/server/middleware/admin-auth.js +38 -0
- package/dist/server/middleware/tenant-proxy.d.ts +56 -0
- package/dist/server/middleware/tenant-proxy.js +162 -0
- package/dist/server/mount-routes.d.ts +30 -0
- package/dist/server/mount-routes.js +74 -0
- package/dist/server/routes/__tests__/admin.test.d.ts +1 -0
- package/dist/server/routes/__tests__/admin.test.js +267 -0
- package/dist/server/routes/__tests__/crypto-webhook.test.d.ts +1 -0
- package/dist/server/routes/__tests__/crypto-webhook.test.js +137 -0
- package/dist/server/routes/__tests__/provision-webhook.test.d.ts +1 -0
- package/dist/server/routes/__tests__/provision-webhook.test.js +212 -0
- package/dist/server/routes/__tests__/stripe-webhook.test.d.ts +1 -0
- package/dist/server/routes/__tests__/stripe-webhook.test.js +65 -0
- package/dist/server/routes/admin.d.ts +111 -0
- package/dist/server/routes/admin.js +273 -0
- package/dist/server/routes/crypto-webhook.d.ts +23 -0
- package/dist/server/routes/crypto-webhook.js +82 -0
- package/dist/server/routes/provision-webhook.d.ts +38 -0
- package/dist/server/routes/provision-webhook.js +160 -0
- package/dist/server/routes/stripe-webhook.d.ts +10 -0
- package/dist/server/routes/stripe-webhook.js +29 -0
- package/dist/server/test-container.d.ts +15 -0
- package/dist/server/test-container.js +103 -0
- package/dist/trpc/auth-helpers.d.ts +17 -0
- package/dist/trpc/auth-helpers.js +26 -0
- package/dist/trpc/container-factories.d.ts +300 -0
- package/dist/trpc/container-factories.js +80 -0
- package/dist/trpc/index.d.ts +2 -0
- package/dist/trpc/index.js +2 -0
- package/package.json +5 -1
- package/src/server/__tests__/build-container.test.ts +402 -0
- package/src/server/__tests__/container.test.ts +204 -0
- package/src/server/__tests__/lifecycle.test.ts +106 -0
- package/src/server/__tests__/mount-routes.test.ts +169 -0
- package/src/server/boot-config.ts +84 -0
- package/src/server/container.ts +237 -0
- package/src/server/index.ts +92 -0
- package/src/server/lifecycle.ts +62 -0
- package/src/server/middleware/__tests__/admin-auth.test.ts +67 -0
- package/src/server/middleware/__tests__/tenant-proxy.test.ts +308 -0
- package/src/server/middleware/admin-auth.ts +51 -0
- package/src/server/middleware/tenant-proxy.ts +192 -0
- package/src/server/mount-routes.ts +113 -0
- package/src/server/routes/__tests__/admin.test.ts +320 -0
- package/src/server/routes/__tests__/crypto-webhook.test.ts +167 -0
- package/src/server/routes/__tests__/provision-webhook.test.ts +323 -0
- package/src/server/routes/__tests__/stripe-webhook.test.ts +73 -0
- package/src/server/routes/admin.ts +334 -0
- package/src/server/routes/crypto-webhook.ts +110 -0
- package/src/server/routes/provision-webhook.ts +212 -0
- package/src/server/routes/stripe-webhook.ts +36 -0
- package/src/server/test-container.ts +120 -0
- package/src/trpc/auth-helpers.ts +28 -0
- package/src/trpc/container-factories.ts +114 -0
- package/src/trpc/index.ts +9 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container-based tRPC router factories.
|
|
3
|
+
*
|
|
4
|
+
* Each function accepts a PlatformContainer (plus any extra deps not yet on
|
|
5
|
+
* the container) and returns a tRPC router by delegating to the existing
|
|
6
|
+
* factory functions. This provides a single-call DI entry point for products
|
|
7
|
+
* that have migrated to the container pattern, while the existing factory
|
|
8
|
+
* functions and setter-based API remain fully functional for products that
|
|
9
|
+
* haven't migrated yet.
|
|
10
|
+
*/
|
|
11
|
+
import { createAdminFleetUpdateRouter } from "./admin-fleet-update-router.js";
|
|
12
|
+
import { createFleetUpdateConfigRouter } from "./fleet-update-config-router.js";
|
|
13
|
+
import { setTrpcOrgMemberRepo } from "./init.js";
|
|
14
|
+
import { createNotificationTemplateRouter } from "./notification-template-router.js";
|
|
15
|
+
import { createOrgRemovePaymentMethodRouter, } from "./org-remove-payment-method-router.js";
|
|
16
|
+
import { createProductConfigRouter } from "./product-config-router.js";
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Init / middleware wiring
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/**
|
|
21
|
+
* Wire the PlatformContainer's orgMemberRepo into the tRPC middleware layer.
|
|
22
|
+
*
|
|
23
|
+
* This replaces the manual `setTrpcOrgMemberRepo()` call. Products using the
|
|
24
|
+
* container call this once at boot; the setter-based API remains for products
|
|
25
|
+
* that haven't migrated yet.
|
|
26
|
+
*/
|
|
27
|
+
export function initTrpcFromContainer(container) {
|
|
28
|
+
setTrpcOrgMemberRepo(container.orgMemberRepo);
|
|
29
|
+
}
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Router factories — thin wrappers over existing factory functions
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
/**
|
|
34
|
+
* Create the admin fleet-update router from a container.
|
|
35
|
+
*
|
|
36
|
+
* Requires additional fleet-specific deps (orchestrator + config repo) that
|
|
37
|
+
* are constructed at boot when fleet is enabled and are not yet on the
|
|
38
|
+
* PlatformContainer itself.
|
|
39
|
+
*/
|
|
40
|
+
export function createAdminFleetUpdateRouterFromContainer(_container, getOrchestrator, getConfigRepo) {
|
|
41
|
+
return createAdminFleetUpdateRouter(getOrchestrator, getConfigRepo);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create the fleet-update-config router from a container.
|
|
45
|
+
*
|
|
46
|
+
* Requires the tenant-update-config repository getter (fleet-specific dep
|
|
47
|
+
* not yet on PlatformContainer).
|
|
48
|
+
*/
|
|
49
|
+
export function createFleetUpdateConfigRouterFromContainer(_container, getConfigRepo) {
|
|
50
|
+
return createFleetUpdateConfigRouter(getConfigRepo);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create the notification-template router from a container.
|
|
54
|
+
*
|
|
55
|
+
* Requires a getter for the notification-template repository (not yet on
|
|
56
|
+
* PlatformContainer).
|
|
57
|
+
*/
|
|
58
|
+
export function createNotificationTemplateRouterFromContainer(_container, getRepo) {
|
|
59
|
+
return createNotificationTemplateRouter(getRepo);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create the org-remove-payment-method router from a container.
|
|
63
|
+
*
|
|
64
|
+
* The container's StripeServices.processor is typed narrowly (webhook-only),
|
|
65
|
+
* so this still requires the full OrgRemovePaymentMethodDeps to be supplied
|
|
66
|
+
* until the container's Stripe sub-container is widened to include
|
|
67
|
+
* IPaymentProcessor.
|
|
68
|
+
*/
|
|
69
|
+
export function createOrgRemovePaymentMethodRouterFromContainer(_container, getDeps) {
|
|
70
|
+
return createOrgRemovePaymentMethodRouter(getDeps);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create the product-config router from a container.
|
|
74
|
+
*
|
|
75
|
+
* Requires a getter for ProductConfigService and the product slug, both of
|
|
76
|
+
* which are product-specific and not on PlatformContainer.
|
|
77
|
+
*/
|
|
78
|
+
export function createProductConfigRouterFromContainer(_container, getService, productSlug) {
|
|
79
|
+
return createProductConfigRouter(getService, productSlug);
|
|
80
|
+
}
|
package/dist/trpc/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { createAdminFleetUpdateRouter } from "./admin-fleet-update-router.js";
|
|
2
|
+
export { createAssertOrgAdminOrOwner } from "./auth-helpers.js";
|
|
2
3
|
export { authSocialRouter } from "./auth-social-router.js";
|
|
4
|
+
export { createAdminFleetUpdateRouterFromContainer, createFleetUpdateConfigRouterFromContainer, createNotificationTemplateRouterFromContainer, createOrgRemovePaymentMethodRouterFromContainer, createProductConfigRouterFromContainer, initTrpcFromContainer, } from "./container-factories.js";
|
|
3
5
|
export { createFleetUpdateConfigRouter } from "./fleet-update-config-router.js";
|
|
4
6
|
export { adminProcedure, createCallerFactory, orgAdminProcedure, orgMemberProcedure, protectedProcedure, publicProcedure, router, setTrpcOrgMemberRepo, type TRPCContext, tenantProcedure, } from "./init.js";
|
|
5
7
|
export { createNotificationTemplateRouter } from "./notification-template-router.js";
|
package/dist/trpc/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { createAdminFleetUpdateRouter } from "./admin-fleet-update-router.js";
|
|
2
|
+
export { createAssertOrgAdminOrOwner } from "./auth-helpers.js";
|
|
2
3
|
export { authSocialRouter } from "./auth-social-router.js";
|
|
4
|
+
export { createAdminFleetUpdateRouterFromContainer, createFleetUpdateConfigRouterFromContainer, createNotificationTemplateRouterFromContainer, createOrgRemovePaymentMethodRouterFromContainer, createProductConfigRouterFromContainer, initTrpcFromContainer, } from "./container-factories.js";
|
|
3
5
|
export { createFleetUpdateConfigRouter } from "./fleet-update-config-router.js";
|
|
4
6
|
export { adminProcedure, createCallerFactory, orgAdminProcedure, orgMemberProcedure, protectedProcedure, publicProcedure, router, setTrpcOrgMemberRepo, tenantProcedure, } from "./init.js";
|
|
5
7
|
export { createNotificationTemplateRouter } from "./notification-template-router.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wopr-network/platform-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.69.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"./product-config": "./dist/product-config/index.js",
|
|
37
37
|
"./proxy": "./dist/proxy/index.js",
|
|
38
38
|
"./security": "./dist/security/index.js",
|
|
39
|
+
"./server": {
|
|
40
|
+
"import": "./dist/server/index.js",
|
|
41
|
+
"types": "./dist/server/index.d.ts"
|
|
42
|
+
},
|
|
39
43
|
"./setup": "./dist/setup/index.js",
|
|
40
44
|
"./tenancy": "./dist/tenancy/index.js",
|
|
41
45
|
"./api/routes/audit": "./dist/api/routes/audit.js",
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { BootConfig } from "../boot-config.js";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Mocks — all class mocks use real classes (not arrow fns) so `new` works.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
// Mock pg.Pool
|
|
9
|
+
const mockPoolEnd = vi.fn().mockResolvedValue(undefined);
|
|
10
|
+
const poolConstructorCalls: Array<{ connectionString: string }> = [];
|
|
11
|
+
class MockPoolClass {
|
|
12
|
+
connectionString: string;
|
|
13
|
+
end = mockPoolEnd;
|
|
14
|
+
query = vi.fn().mockResolvedValue({ rows: [] });
|
|
15
|
+
constructor(opts: { connectionString: string }) {
|
|
16
|
+
this.connectionString = opts.connectionString;
|
|
17
|
+
poolConstructorCalls.push(opts);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
vi.mock("pg", () => ({ Pool: MockPoolClass }));
|
|
21
|
+
|
|
22
|
+
// Mock drizzle db creation
|
|
23
|
+
const mockDb = { __brand: "drizzle-db" } as never;
|
|
24
|
+
vi.mock("../../db/index.js", () => ({
|
|
25
|
+
createDb: vi.fn(() => mockDb),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock drizzle migrator
|
|
29
|
+
const mockMigrate = vi.fn().mockResolvedValue(undefined);
|
|
30
|
+
vi.mock("drizzle-orm/node-postgres/migrator", () => ({
|
|
31
|
+
migrate: mockMigrate,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Mock platformBoot
|
|
35
|
+
const mockProductConfig = {
|
|
36
|
+
product: {
|
|
37
|
+
slug: "test",
|
|
38
|
+
brandName: "Test",
|
|
39
|
+
domain: "test.dev",
|
|
40
|
+
appDomain: "app.test.dev",
|
|
41
|
+
fromEmail: "hi@test.dev",
|
|
42
|
+
emailSupport: "support@test.dev",
|
|
43
|
+
},
|
|
44
|
+
navItems: [],
|
|
45
|
+
domains: [],
|
|
46
|
+
features: null,
|
|
47
|
+
fleet: null,
|
|
48
|
+
billing: null,
|
|
49
|
+
};
|
|
50
|
+
const mockPlatformBoot = vi.fn().mockResolvedValue({
|
|
51
|
+
service: {},
|
|
52
|
+
config: mockProductConfig,
|
|
53
|
+
corsOrigins: ["https://test.dev"],
|
|
54
|
+
seeded: false,
|
|
55
|
+
});
|
|
56
|
+
vi.mock("../../product-config/boot.js", () => ({
|
|
57
|
+
platformBoot: (...args: unknown[]) => mockPlatformBoot(...args),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Mock DrizzleLedger
|
|
61
|
+
const mockSeedSystemAccounts = vi.fn().mockResolvedValue(undefined);
|
|
62
|
+
class MockDrizzleLedgerClass {
|
|
63
|
+
seedSystemAccounts = mockSeedSystemAccounts;
|
|
64
|
+
balance = vi.fn().mockResolvedValue(0);
|
|
65
|
+
credit = vi.fn();
|
|
66
|
+
debit = vi.fn();
|
|
67
|
+
post = vi.fn();
|
|
68
|
+
hasReferenceId = vi.fn().mockResolvedValue(false);
|
|
69
|
+
history = vi.fn().mockResolvedValue([]);
|
|
70
|
+
tenantsWithBalance = vi.fn().mockResolvedValue([]);
|
|
71
|
+
memberUsage = vi.fn().mockResolvedValue([]);
|
|
72
|
+
lifetimeSpend = vi.fn().mockResolvedValue(0);
|
|
73
|
+
lifetimeSpendBatch = vi.fn().mockResolvedValue(new Map());
|
|
74
|
+
expiredCredits = vi.fn().mockResolvedValue([]);
|
|
75
|
+
trialBalance = vi.fn().mockResolvedValue({ balanced: true });
|
|
76
|
+
accountBalance = vi.fn().mockResolvedValue(0);
|
|
77
|
+
existsByReferenceIdLike = vi.fn().mockResolvedValue(false);
|
|
78
|
+
sumPurchasesForPeriod = vi.fn().mockResolvedValue(0);
|
|
79
|
+
getActiveTenantIdsInWindow = vi.fn().mockResolvedValue([]);
|
|
80
|
+
debitCapped = vi.fn().mockResolvedValue(null);
|
|
81
|
+
}
|
|
82
|
+
vi.mock("../../credits/ledger.js", async (importOriginal) => {
|
|
83
|
+
const orig = await importOriginal<Record<string, unknown>>();
|
|
84
|
+
return { ...orig, DrizzleLedger: MockDrizzleLedgerClass };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Mock org/tenancy
|
|
88
|
+
class MockDrizzleOrgMemberRepositoryClass {
|
|
89
|
+
listMembers = vi.fn().mockResolvedValue([]);
|
|
90
|
+
addMember = vi.fn();
|
|
91
|
+
}
|
|
92
|
+
vi.mock("../../tenancy/org-member-repository.js", () => ({
|
|
93
|
+
DrizzleOrgMemberRepository: MockDrizzleOrgMemberRepositoryClass,
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
class MockDrizzleOrgRepositoryClass {}
|
|
97
|
+
vi.mock("../../tenancy/drizzle-org-repository.js", () => ({
|
|
98
|
+
DrizzleOrgRepository: MockDrizzleOrgRepositoryClass,
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
const orgServiceConstructorCalls: unknown[][] = [];
|
|
102
|
+
class MockOrgServiceClass {
|
|
103
|
+
getOrCreatePersonalOrg = vi.fn();
|
|
104
|
+
constructor(...args: unknown[]) {
|
|
105
|
+
orgServiceConstructorCalls.push(args);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
vi.mock("../../tenancy/org-service.js", () => ({
|
|
109
|
+
OrgService: MockOrgServiceClass,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
// Mock auth
|
|
113
|
+
class MockBetterAuthUserRepositoryClass {}
|
|
114
|
+
vi.mock("../../db/auth-user-repository.js", () => ({
|
|
115
|
+
BetterAuthUserRepository: MockBetterAuthUserRepositoryClass,
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
class MockDrizzleUserRoleRepositoryClass {
|
|
119
|
+
isPlatformAdmin = vi.fn().mockResolvedValue(false);
|
|
120
|
+
listRolesByUser = vi.fn().mockResolvedValue([]);
|
|
121
|
+
getTenantIdByUserId = vi.fn().mockResolvedValue(null);
|
|
122
|
+
grantRole = vi.fn();
|
|
123
|
+
revokeRole = vi.fn().mockResolvedValue(false);
|
|
124
|
+
listUsersByRole = vi.fn().mockResolvedValue([]);
|
|
125
|
+
}
|
|
126
|
+
vi.mock("../../auth/user-role-repository.js", () => ({
|
|
127
|
+
DrizzleUserRoleRepository: MockDrizzleUserRoleRepositoryClass,
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
// Mock fleet deps (only imported when fleet is enabled)
|
|
131
|
+
class MockFleetManagerClass {
|
|
132
|
+
__brand = "fleet-manager";
|
|
133
|
+
}
|
|
134
|
+
vi.mock("../../fleet/fleet-manager.js", () => ({
|
|
135
|
+
FleetManager: MockFleetManagerClass,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
class MockProfileStoreClass {
|
|
139
|
+
__brand = "profile-store";
|
|
140
|
+
}
|
|
141
|
+
vi.mock("../../fleet/profile-store.js", () => ({
|
|
142
|
+
ProfileStore: MockProfileStoreClass,
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
class MockProxyManagerClass {
|
|
146
|
+
__brand = "proxy-manager";
|
|
147
|
+
}
|
|
148
|
+
vi.mock("../../proxy/manager.js", () => ({
|
|
149
|
+
ProxyManager: MockProxyManagerClass,
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
class MockDrizzleServiceKeyRepositoryClass {
|
|
153
|
+
__brand = "service-key-repo";
|
|
154
|
+
}
|
|
155
|
+
vi.mock("../../gateway/service-key-repository.js", () => ({
|
|
156
|
+
DrizzleServiceKeyRepository: MockDrizzleServiceKeyRepositoryClass,
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
class MockDockerClass {
|
|
160
|
+
__brand = "docker";
|
|
161
|
+
}
|
|
162
|
+
vi.mock("dockerode", () => ({
|
|
163
|
+
default: MockDockerClass,
|
|
164
|
+
}));
|
|
165
|
+
|
|
166
|
+
// Mock crypto deps
|
|
167
|
+
class MockDrizzleCryptoChargeRepositoryClass {
|
|
168
|
+
__brand = "charge-repo";
|
|
169
|
+
}
|
|
170
|
+
vi.mock("../../billing/crypto/charge-store.js", () => ({
|
|
171
|
+
DrizzleCryptoChargeRepository: MockDrizzleCryptoChargeRepositoryClass,
|
|
172
|
+
}));
|
|
173
|
+
|
|
174
|
+
class MockDrizzleWebhookSeenRepositoryClass {
|
|
175
|
+
__brand = "webhook-seen-repo";
|
|
176
|
+
}
|
|
177
|
+
vi.mock("../../billing/drizzle-webhook-seen-repository.js", () => ({
|
|
178
|
+
DrizzleWebhookSeenRepository: MockDrizzleWebhookSeenRepositoryClass,
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
// Mock stripe deps
|
|
182
|
+
class MockStripeClass {
|
|
183
|
+
__brand = "stripe-client";
|
|
184
|
+
}
|
|
185
|
+
vi.mock("stripe", () => ({
|
|
186
|
+
default: MockStripeClass,
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
class MockDrizzleTenantCustomerRepositoryClass {
|
|
190
|
+
__brand = "tenant-customer-repo";
|
|
191
|
+
}
|
|
192
|
+
vi.mock("../../billing/stripe/tenant-store.js", () => ({
|
|
193
|
+
DrizzleTenantCustomerRepository: MockDrizzleTenantCustomerRepositoryClass,
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
vi.mock("../../billing/stripe/credit-prices.js", () => ({
|
|
197
|
+
loadCreditPriceMap: vi.fn(() => new Map()),
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
class MockStripePaymentProcessorClass {
|
|
201
|
+
__brand = "stripe-processor";
|
|
202
|
+
handleWebhook = vi.fn();
|
|
203
|
+
}
|
|
204
|
+
vi.mock("../../billing/stripe/stripe-payment-processor.js", () => ({
|
|
205
|
+
StripePaymentProcessor: MockStripePaymentProcessorClass,
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Helpers
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
function baseBootConfig(overrides?: Partial<BootConfig>): BootConfig {
|
|
213
|
+
return {
|
|
214
|
+
slug: "test-product",
|
|
215
|
+
databaseUrl: "postgres://localhost:5432/testdb",
|
|
216
|
+
provisionSecret: "test-secret",
|
|
217
|
+
features: {
|
|
218
|
+
fleet: false,
|
|
219
|
+
crypto: false,
|
|
220
|
+
stripe: false,
|
|
221
|
+
gateway: false,
|
|
222
|
+
hotPool: false,
|
|
223
|
+
},
|
|
224
|
+
...overrides,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Tests
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
describe("buildContainer", () => {
|
|
233
|
+
// Dynamic import so mocks are registered before the module loads
|
|
234
|
+
async function loadBuildContainer() {
|
|
235
|
+
const mod = await import("../container.js");
|
|
236
|
+
return mod.buildContainer;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
it("throws on empty databaseUrl", async () => {
|
|
240
|
+
const buildContainer = await loadBuildContainer();
|
|
241
|
+
const config = baseBootConfig({ databaseUrl: "" });
|
|
242
|
+
|
|
243
|
+
await expect(buildContainer(config)).rejects.toThrow("databaseUrl is required");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("creates pool with correct connectionString", async () => {
|
|
247
|
+
const buildContainer = await loadBuildContainer();
|
|
248
|
+
const config = baseBootConfig();
|
|
249
|
+
poolConstructorCalls.length = 0;
|
|
250
|
+
|
|
251
|
+
const container = await buildContainer(config);
|
|
252
|
+
|
|
253
|
+
expect(poolConstructorCalls).toContainEqual({
|
|
254
|
+
connectionString: "postgres://localhost:5432/testdb",
|
|
255
|
+
});
|
|
256
|
+
expect(container.pool).toBeDefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("calls platformBoot with correct slug", async () => {
|
|
260
|
+
const buildContainer = await loadBuildContainer();
|
|
261
|
+
const config = baseBootConfig({ slug: "paperclip" });
|
|
262
|
+
|
|
263
|
+
await buildContainer(config);
|
|
264
|
+
|
|
265
|
+
expect(mockPlatformBoot).toHaveBeenCalledWith(expect.objectContaining({ slug: "paperclip" }));
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("seeds system accounts after creating ledger", async () => {
|
|
269
|
+
const buildContainer = await loadBuildContainer();
|
|
270
|
+
const config = baseBootConfig();
|
|
271
|
+
|
|
272
|
+
await buildContainer(config);
|
|
273
|
+
|
|
274
|
+
expect(mockSeedSystemAccounts).toHaveBeenCalled();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("core services are always present", async () => {
|
|
278
|
+
const buildContainer = await loadBuildContainer();
|
|
279
|
+
const config = baseBootConfig();
|
|
280
|
+
|
|
281
|
+
const container = await buildContainer(config);
|
|
282
|
+
|
|
283
|
+
expect(container.db).toBeDefined();
|
|
284
|
+
expect(container.pool).toBeDefined();
|
|
285
|
+
expect(container.productConfig).toBeDefined();
|
|
286
|
+
expect(container.creditLedger).toBeDefined();
|
|
287
|
+
expect(container.orgMemberRepo).toBeDefined();
|
|
288
|
+
expect(container.orgService).toBeDefined();
|
|
289
|
+
expect(container.userRoleRepo).toBeDefined();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("returns null for all disabled features", async () => {
|
|
293
|
+
const buildContainer = await loadBuildContainer();
|
|
294
|
+
const config = baseBootConfig();
|
|
295
|
+
|
|
296
|
+
const container = await buildContainer(config);
|
|
297
|
+
|
|
298
|
+
expect(container.fleet).toBeNull();
|
|
299
|
+
expect(container.crypto).toBeNull();
|
|
300
|
+
expect(container.stripe).toBeNull();
|
|
301
|
+
expect(container.gateway).toBeNull();
|
|
302
|
+
expect(container.hotPool).toBeNull();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("builds fleet services when feature is enabled", async () => {
|
|
306
|
+
const buildContainer = await loadBuildContainer();
|
|
307
|
+
const config = baseBootConfig({
|
|
308
|
+
features: { fleet: true, crypto: false, stripe: false, gateway: false, hotPool: false },
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const container = await buildContainer(config);
|
|
312
|
+
|
|
313
|
+
expect(container.fleet).not.toBeNull();
|
|
314
|
+
expect(container.fleet?.manager).toBeDefined();
|
|
315
|
+
expect(container.fleet?.docker).toBeDefined();
|
|
316
|
+
expect(container.fleet?.proxy).toBeDefined();
|
|
317
|
+
expect(container.fleet?.profileStore).toBeDefined();
|
|
318
|
+
expect(container.fleet?.serviceKeyRepo).toBeDefined();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("builds crypto services when feature is enabled", async () => {
|
|
322
|
+
const buildContainer = await loadBuildContainer();
|
|
323
|
+
const config = baseBootConfig({
|
|
324
|
+
features: { fleet: false, crypto: true, stripe: false, gateway: false, hotPool: false },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const container = await buildContainer(config);
|
|
328
|
+
|
|
329
|
+
expect(container.crypto).not.toBeNull();
|
|
330
|
+
expect(container.crypto?.chargeRepo).toBeDefined();
|
|
331
|
+
expect(container.crypto?.webhookSeenRepo).toBeDefined();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("builds stripe services when feature is enabled and key provided", async () => {
|
|
335
|
+
const buildContainer = await loadBuildContainer();
|
|
336
|
+
const config = baseBootConfig({
|
|
337
|
+
features: { fleet: false, crypto: false, stripe: true, gateway: false, hotPool: false },
|
|
338
|
+
stripeSecretKey: "sk_test_123",
|
|
339
|
+
stripeWebhookSecret: "whsec_test_456",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const container = await buildContainer(config);
|
|
343
|
+
|
|
344
|
+
expect(container.stripe).not.toBeNull();
|
|
345
|
+
expect(container.stripe?.stripe).toBeDefined();
|
|
346
|
+
expect(container.stripe?.webhookSecret).toBe("whsec_test_456");
|
|
347
|
+
expect(container.stripe?.customerRepo).toBeDefined();
|
|
348
|
+
expect(container.stripe?.processor).toBeDefined();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("returns null stripe when feature enabled but no secret key", async () => {
|
|
352
|
+
const buildContainer = await loadBuildContainer();
|
|
353
|
+
const config = baseBootConfig({
|
|
354
|
+
features: { fleet: false, crypto: false, stripe: true, gateway: false, hotPool: false },
|
|
355
|
+
// stripeSecretKey intentionally omitted
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const container = await buildContainer(config);
|
|
359
|
+
|
|
360
|
+
expect(container.stripe).toBeNull();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("builds gateway services when feature is enabled", async () => {
|
|
364
|
+
const buildContainer = await loadBuildContainer();
|
|
365
|
+
const config = baseBootConfig({
|
|
366
|
+
features: { fleet: false, crypto: false, stripe: false, gateway: true, hotPool: false },
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const container = await buildContainer(config);
|
|
370
|
+
|
|
371
|
+
expect(container.gateway).not.toBeNull();
|
|
372
|
+
expect(container.gateway?.serviceKeyRepo).toBeDefined();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("runs migrations before building services", async () => {
|
|
376
|
+
const buildContainer = await loadBuildContainer();
|
|
377
|
+
const config = baseBootConfig();
|
|
378
|
+
|
|
379
|
+
await buildContainer(config);
|
|
380
|
+
|
|
381
|
+
expect(mockMigrate).toHaveBeenCalled();
|
|
382
|
+
// Migration should be called before platformBoot
|
|
383
|
+
const migrateOrder = mockMigrate.mock.invocationCallOrder[0];
|
|
384
|
+
const bootOrder = mockPlatformBoot.mock.invocationCallOrder[0];
|
|
385
|
+
expect(migrateOrder).toBeLessThan(bootOrder);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("constructs OrgService with orgRepo, memberRepo, db, and authUserRepo", async () => {
|
|
389
|
+
const buildContainer = await loadBuildContainer();
|
|
390
|
+
const config = baseBootConfig();
|
|
391
|
+
orgServiceConstructorCalls.length = 0;
|
|
392
|
+
|
|
393
|
+
await buildContainer(config);
|
|
394
|
+
|
|
395
|
+
expect(orgServiceConstructorCalls.length).toBeGreaterThan(0);
|
|
396
|
+
const [orgRepo, memberRepo, db, options] = orgServiceConstructorCalls[0];
|
|
397
|
+
expect(orgRepo).toBeInstanceOf(MockDrizzleOrgRepositoryClass);
|
|
398
|
+
expect(memberRepo).toBeInstanceOf(MockDrizzleOrgMemberRepositoryClass);
|
|
399
|
+
expect(db).toBe(mockDb);
|
|
400
|
+
expect(options).toEqual(expect.objectContaining({ userRepo: expect.any(MockBetterAuthUserRepositoryClass) }));
|
|
401
|
+
});
|
|
402
|
+
});
|