@wopr-network/platform-core 1.35.0 → 1.35.2
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/billing/crypto/btc/settler.js +3 -2
- package/dist/billing/crypto/evm/eth-settler.js +3 -1
- package/dist/billing/crypto/unified-checkout.js +1 -1
- package/dist/email/handlebars-renderer.test.d.ts +1 -0
- package/dist/email/handlebars-renderer.test.js +174 -0
- package/dist/fleet/fleet-notification-listener.test.d.ts +1 -0
- package/dist/fleet/fleet-notification-listener.test.js +244 -0
- package/dist/fleet/init-fleet-updater.d.ts +1 -1
- package/dist/fleet/init-fleet-updater.js +19 -3
- package/dist/fleet/init-fleet-updater.test.d.ts +1 -0
- package/dist/fleet/init-fleet-updater.test.js +186 -0
- package/dist/trpc/admin-fleet-update-router.test.d.ts +1 -0
- package/dist/trpc/admin-fleet-update-router.test.js +164 -0
- package/package.json +1 -1
- package/src/billing/crypto/btc/settler.ts +3 -2
- package/src/billing/crypto/evm/eth-settler.ts +3 -1
- package/src/billing/crypto/unified-checkout.ts +1 -1
- package/src/email/handlebars-renderer.test.ts +209 -0
- package/src/fleet/fleet-notification-listener.test.ts +322 -0
- package/src/fleet/init-fleet-updater.test.ts +234 -0
- package/src/fleet/init-fleet-updater.ts +22 -4
- package/src/trpc/admin-fleet-update-router.test.ts +201 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { RolloutOrchestrator } from "../fleet/rollout-orchestrator.js";
|
|
3
|
+
import type { ITenantUpdateConfigRepository, TenantUpdateConfig } from "../fleet/tenant-update-config-repository.js";
|
|
4
|
+
import type { IOrgMemberRepository } from "../tenancy/org-member-repository.js";
|
|
5
|
+
import { createAdminFleetUpdateRouter } from "./admin-fleet-update-router.js";
|
|
6
|
+
import { createCallerFactory, router, setTrpcOrgMemberRepo } from "./init.js";
|
|
7
|
+
|
|
8
|
+
vi.mock("../config/logger.js", () => ({
|
|
9
|
+
logger: {
|
|
10
|
+
info: vi.fn(),
|
|
11
|
+
error: vi.fn(),
|
|
12
|
+
warn: vi.fn(),
|
|
13
|
+
debug: vi.fn(),
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Helpers
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
function makeMockOrgRepo(): IOrgMemberRepository {
|
|
22
|
+
return {
|
|
23
|
+
listMembers: vi.fn().mockResolvedValue([]),
|
|
24
|
+
addMember: vi.fn().mockResolvedValue(undefined),
|
|
25
|
+
updateMemberRole: vi.fn().mockResolvedValue(undefined),
|
|
26
|
+
removeMember: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
findMember: vi.fn().mockResolvedValue(null),
|
|
28
|
+
countAdminsAndOwners: vi.fn().mockResolvedValue(0),
|
|
29
|
+
listInvites: vi.fn().mockResolvedValue([]),
|
|
30
|
+
createInvite: vi.fn().mockResolvedValue(undefined),
|
|
31
|
+
findInviteById: vi.fn().mockResolvedValue(null),
|
|
32
|
+
findInviteByToken: vi.fn().mockResolvedValue(null),
|
|
33
|
+
deleteInvite: vi.fn().mockResolvedValue(undefined),
|
|
34
|
+
deleteAllMembers: vi.fn().mockResolvedValue(undefined),
|
|
35
|
+
deleteAllInvites: vi.fn().mockResolvedValue(undefined),
|
|
36
|
+
listOrgsByUser: vi.fn().mockResolvedValue([]),
|
|
37
|
+
markInviteAccepted: vi.fn().mockResolvedValue(undefined),
|
|
38
|
+
} as unknown as IOrgMemberRepository;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeOrchestrator(overrides: Partial<RolloutOrchestrator> = {}): RolloutOrchestrator {
|
|
42
|
+
return {
|
|
43
|
+
isRolling: false,
|
|
44
|
+
rollout: vi.fn().mockResolvedValue({
|
|
45
|
+
totalBots: 0,
|
|
46
|
+
succeeded: 0,
|
|
47
|
+
failed: 0,
|
|
48
|
+
skipped: 0,
|
|
49
|
+
aborted: false,
|
|
50
|
+
alreadyRunning: false,
|
|
51
|
+
results: [],
|
|
52
|
+
}),
|
|
53
|
+
...overrides,
|
|
54
|
+
} as unknown as RolloutOrchestrator;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function makeConfigRepo(configs: Record<string, TenantUpdateConfig> = {}): ITenantUpdateConfigRepository {
|
|
58
|
+
return {
|
|
59
|
+
get: vi.fn().mockImplementation((tenantId: string) => Promise.resolve(configs[tenantId] ?? null)),
|
|
60
|
+
upsert: vi.fn().mockResolvedValue(undefined),
|
|
61
|
+
listAutoEnabled: vi.fn().mockResolvedValue(Object.values(configs)),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const adminCtx = {
|
|
66
|
+
user: { id: "admin-1", roles: ["platform_admin"] },
|
|
67
|
+
tenantId: undefined,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Tests
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
describe("createAdminFleetUpdateRouter", () => {
|
|
75
|
+
let orchestrator: RolloutOrchestrator;
|
|
76
|
+
let configRepo: ITenantUpdateConfigRepository;
|
|
77
|
+
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
setTrpcOrgMemberRepo(makeMockOrgRepo());
|
|
80
|
+
orchestrator = makeOrchestrator();
|
|
81
|
+
configRepo = makeConfigRepo();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function makeCaller() {
|
|
85
|
+
const fleetRouter = createAdminFleetUpdateRouter(
|
|
86
|
+
() => orchestrator,
|
|
87
|
+
() => configRepo,
|
|
88
|
+
);
|
|
89
|
+
const appRouter = router({ fleet: fleetRouter });
|
|
90
|
+
const createCaller = createCallerFactory(appRouter);
|
|
91
|
+
return createCaller(adminCtx);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe("rolloutStatus", () => {
|
|
95
|
+
it("returns isRolling from orchestrator (false)", async () => {
|
|
96
|
+
const caller = makeCaller();
|
|
97
|
+
const status = await caller.fleet.rolloutStatus();
|
|
98
|
+
expect(status).toEqual({ isRolling: false });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns isRolling from orchestrator (true)", async () => {
|
|
102
|
+
orchestrator = makeOrchestrator({ isRolling: true } as Partial<RolloutOrchestrator>);
|
|
103
|
+
const caller = makeCaller();
|
|
104
|
+
const status = await caller.fleet.rolloutStatus();
|
|
105
|
+
expect(status).toEqual({ isRolling: true });
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("forceRollout", () => {
|
|
110
|
+
it("calls orchestrator.rollout()", async () => {
|
|
111
|
+
const caller = makeCaller();
|
|
112
|
+
const result = await caller.fleet.forceRollout();
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual({ triggered: true });
|
|
115
|
+
expect(orchestrator.rollout).toHaveBeenCalledOnce();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("listTenantConfigs", () => {
|
|
120
|
+
it("delegates to repo.listAutoEnabled()", async () => {
|
|
121
|
+
const configs: TenantUpdateConfig[] = [
|
|
122
|
+
{ tenantId: "t1", mode: "auto", preferredHourUtc: 3, updatedAt: Date.now() },
|
|
123
|
+
{ tenantId: "t2", mode: "auto", preferredHourUtc: 12, updatedAt: Date.now() },
|
|
124
|
+
];
|
|
125
|
+
vi.mocked(configRepo.listAutoEnabled).mockResolvedValue(configs);
|
|
126
|
+
|
|
127
|
+
const caller = makeCaller();
|
|
128
|
+
const result = await caller.fleet.listTenantConfigs();
|
|
129
|
+
|
|
130
|
+
expect(configRepo.listAutoEnabled).toHaveBeenCalledOnce();
|
|
131
|
+
expect(result).toEqual(configs);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("setTenantConfig", () => {
|
|
136
|
+
it("preserves existing preferredHourUtc when not provided", async () => {
|
|
137
|
+
const existing: TenantUpdateConfig = {
|
|
138
|
+
tenantId: "t1",
|
|
139
|
+
mode: "auto",
|
|
140
|
+
preferredHourUtc: 17,
|
|
141
|
+
updatedAt: Date.now(),
|
|
142
|
+
};
|
|
143
|
+
vi.mocked(configRepo.get).mockResolvedValue(existing);
|
|
144
|
+
|
|
145
|
+
const caller = makeCaller();
|
|
146
|
+
await caller.fleet.setTenantConfig({ tenantId: "t1", mode: "manual" });
|
|
147
|
+
|
|
148
|
+
expect(configRepo.get).toHaveBeenCalledWith("t1");
|
|
149
|
+
expect(configRepo.upsert).toHaveBeenCalledWith("t1", {
|
|
150
|
+
mode: "manual",
|
|
151
|
+
preferredHourUtc: 17, // preserved from existing
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("uses provided preferredHourUtc when given", async () => {
|
|
156
|
+
const existing: TenantUpdateConfig = {
|
|
157
|
+
tenantId: "t1",
|
|
158
|
+
mode: "auto",
|
|
159
|
+
preferredHourUtc: 17,
|
|
160
|
+
updatedAt: Date.now(),
|
|
161
|
+
};
|
|
162
|
+
vi.mocked(configRepo.get).mockResolvedValue(existing);
|
|
163
|
+
|
|
164
|
+
const caller = makeCaller();
|
|
165
|
+
await caller.fleet.setTenantConfig({
|
|
166
|
+
tenantId: "t1",
|
|
167
|
+
mode: "auto",
|
|
168
|
+
preferredHourUtc: 9,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(configRepo.upsert).toHaveBeenCalledWith("t1", {
|
|
172
|
+
mode: "auto",
|
|
173
|
+
preferredHourUtc: 9,
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("defaults to 3 when no existing config and preferredHourUtc not provided", async () => {
|
|
178
|
+
vi.mocked(configRepo.get).mockResolvedValue(null);
|
|
179
|
+
|
|
180
|
+
const caller = makeCaller();
|
|
181
|
+
await caller.fleet.setTenantConfig({ tenantId: "t-new", mode: "auto" });
|
|
182
|
+
|
|
183
|
+
expect(configRepo.upsert).toHaveBeenCalledWith("t-new", {
|
|
184
|
+
mode: "auto",
|
|
185
|
+
preferredHourUtc: 3, // default
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("rejects non-admin users", async () => {
|
|
191
|
+
const fleetRouter = createAdminFleetUpdateRouter(
|
|
192
|
+
() => orchestrator,
|
|
193
|
+
() => configRepo,
|
|
194
|
+
);
|
|
195
|
+
const appRouter = router({ fleet: fleetRouter });
|
|
196
|
+
const createCaller = createCallerFactory(appRouter);
|
|
197
|
+
const caller = createCaller({ user: { id: "u1", roles: ["user"] }, tenantId: undefined });
|
|
198
|
+
|
|
199
|
+
await expect(caller.fleet.rolloutStatus()).rejects.toThrow("Platform admin role required");
|
|
200
|
+
});
|
|
201
|
+
});
|