@kumori/aurora-backend-handler 1.1.42 → 1.1.43

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.
@@ -1,792 +1,1054 @@
1
- import {
2
- acceptInvite,
3
- createTenant,
4
- rejectInvite,
5
- } from "../api/tenant-api-service";
6
- import { loadUser } from "../api/user-api-service";
7
- import { createAccount } from "../api/account-api-service";
8
- import { createEnvironment } from "../api/environment-api-service";
9
- import { changeRevision, deployService, requestRevisionData, updateService } from "../api/service-api-service";
10
- import {
11
- deployMarketplaceItem,
12
- getMarketplaceItems,
13
- } from "../api/marketplace-api-service";
14
1
  import { BackendHandler } from "../backend-handler";
15
- import { Account, Tenant, UserData, Environment, Service, MarketplaceService } from "@kumori/aurora-interfaces";
2
+ import type { Account, Environment, MarketplaceItem, MarketplaceService, Resource, Service, Tenant, UserData } from "@kumori/aurora-interfaces";
16
3
 
17
- jest.mock("../event-helper", () => {
18
- return jest.fn().mockImplementation((globalEventHandler) => {
19
- return globalEventHandler;
20
- });
21
- });
4
+ // ── Mocks de módulos externos ─────────────────────────────────────────────────
5
+
6
+ jest.mock("../event-helper", () =>
7
+ jest.fn().mockImplementation((globalEventHandler: any) => globalEventHandler),
8
+ );
9
+
10
+ jest.mock("../api/user-api-service", () => ({
11
+ getUserHTTP: jest.fn().mockResolvedValue({}),
12
+ loadUser: jest.fn(),
13
+ isUserLogged: jest.fn().mockResolvedValue(true),
14
+ createUser: jest.fn(),
15
+ updateUser: jest.fn(),
16
+ deleteUSer: jest.fn(),
17
+ }));
18
+
19
+ jest.mock("../api/tenant-api-service", () => ({
20
+ createTenant: jest.fn(),
21
+ createTenantHTTP: jest.fn(),
22
+ updateTenant: jest.fn(),
23
+ updateTenantHTTP: jest.fn(),
24
+ deleteTenant: jest.fn(),
25
+ createRegistry: jest.fn(),
26
+ updateRegistry: jest.fn(),
27
+ deleteRegistry: jest.fn(),
28
+ inviteUser: jest.fn(),
29
+ removeUser: jest.fn(),
30
+ updateUserRole: jest.fn(),
31
+ acceptInvite: jest.fn(),
32
+ rejectInvite: jest.fn(),
33
+ createToken: jest.fn(),
34
+ deleteToken: jest.fn(),
35
+ }));
36
+
37
+ jest.mock("../api/account-api-service", () => ({
38
+ createAccount: jest.fn(),
39
+ updateAccount: jest.fn(),
40
+ deleteAccount: jest.fn(),
41
+ clearAccount: jest.fn(),
42
+ }));
22
43
 
23
- jest.mock("../api/tenant-api-service", () => ({ createTenant: jest.fn() }));
24
- jest.mock("../api/user-api-service", () => ({ loadUser: jest.fn() }));
25
- jest.mock("../api/account-api-service", () => ({ createAccount: jest.fn() }));
26
44
  jest.mock("../api/environment-api-service", () => ({
27
45
  createEnvironment: jest.fn(),
46
+ updateEnvironment: jest.fn(),
47
+ deleteEnvironment: jest.fn(),
48
+ clearEnvironment: jest.fn(),
49
+ scaleEnvironment: jest.fn(),
50
+ }));
51
+
52
+ jest.mock("../api/service-api-service", () => ({
53
+ deployService: jest.fn(),
54
+ updateService: jest.fn(),
55
+ deleteService: jest.fn(),
56
+ restartService: jest.fn(),
57
+ redeployService: jest.fn(),
58
+ requestRevisionData: jest.fn(),
59
+ updateServiceLinks: jest.fn(),
60
+ changeRevision: jest.fn(),
61
+ restartInstance: jest.fn(),
28
62
  }));
29
- jest.mock("../api/service-api-service", () => ({ deployService: jest.fn() }));
63
+
30
64
  jest.mock("../api/marketplace-api-service", () => ({
31
65
  deployMarketplaceItem: jest.fn(),
32
- getMarketplaceItems: jest.fn(() =>
33
- Promise.resolve({ items: ["item1", "item2"] })
34
- ),
66
+ getMarketplaceItems: jest.fn().mockResolvedValue({ items: [] }),
67
+ getMarketplaceSchema: jest.fn(),
68
+ }));
69
+
70
+ jest.mock("../api/resources-api-service", () => ({
71
+ createResource: jest.fn(),
72
+ updateResource: jest.fn(),
73
+ deleteResource: jest.fn(),
74
+ }));
75
+
76
+ jest.mock("../api/planProvider-api-service", () => ({
77
+ getPlanProviders: jest.fn(),
78
+ }));
79
+
80
+ jest.mock("../websocket-manager", () => ({
81
+ initializeGlobalWebSocketClient: jest.fn(),
82
+ updateUserComplete: jest.fn(),
83
+ fetchAndStoreMarketplaceSchema: jest.fn().mockResolvedValue(undefined),
84
+ getReferenceDomain: jest.fn().mockReturnValue("test-domain.com"),
35
85
  }));
36
86
 
37
- const createDummyGlobalEventHandler = () => ({
87
+ // ── Helpers para construir el dummy handler ───────────────────────────────────
88
+
89
+ const makeFns = (...names: string[]) =>
90
+ Object.fromEntries(names.map((n) => [n, jest.fn()]));
91
+
92
+ const createDummyHandler = () => ({
38
93
  subscribe: jest.fn(),
39
94
  publish: jest.fn(),
40
95
  unsubscribe: jest.fn(),
96
+
97
+ notification: {
98
+ publish: makeFns("creation", "deletion", "read"),
99
+ subscribe: makeFns("creation", "deletion", "read"),
100
+ unsubscribe: makeFns("creation", "deletion", "read"),
101
+ },
102
+
41
103
  tenant: {
42
- subscribe: {
43
- creation: jest.fn(),
44
- created: jest.fn(),
45
- creationError: jest.fn(),
46
- update: jest.fn(),
47
- updated: jest.fn(),
48
- updateError: jest.fn(),
49
- delete: jest.fn(),
50
- deleted: jest.fn(),
51
- deletionError: jest.fn(),
52
- createRegistry: jest.fn(),
53
- registryCreated: jest.fn(),
54
- registryCreationError: jest.fn(),
55
- updateRegistry: jest.fn(),
56
- registryUpdated: jest.fn(),
57
- registryUpdateError: jest.fn(),
58
- deleteRegistry: jest.fn(),
59
- registryDeleted: jest.fn(),
60
- registryDeletionError: jest.fn(),
61
- inviteUser: jest.fn(),
62
- userInvited: jest.fn(),
63
- inviteError: jest.fn(),
64
- removeUser: jest.fn(),
65
- userRemoved: jest.fn(),
66
- removeUserError: jest.fn(),
67
- acceptInvite: jest.fn(),
68
- inviteAccepted: jest.fn(),
69
- acceptInviteError: jest.fn(),
70
- updateInvite: jest.fn(),
71
- inviteUpdated: jest.fn(),
72
- inviteUpdateError: jest.fn(),
73
- rejectInvite: jest.fn(),
74
- inviteRejected: jest.fn(),
75
- inviteRejectError: jest.fn(),
76
- },
77
- unsubscribe: {
78
- creation: jest.fn(),
79
- created: jest.fn(),
80
- creationError: jest.fn(),
81
- update: jest.fn(),
82
- updated: jest.fn(),
83
- updateError: jest.fn(),
84
- delete: jest.fn(),
85
- deleted: jest.fn(),
86
- deletionError: jest.fn(),
87
- createRegistry: jest.fn(),
88
- registryCreated: jest.fn(),
89
- registryCreationError: jest.fn(),
90
- updateRegistry: jest.fn(),
91
- registryUpdated: jest.fn(),
92
- registryUpdateError: jest.fn(),
93
- deleteRegistry: jest.fn(),
94
- registryDeleted: jest.fn(),
95
- registryDeletionError: jest.fn(),
96
- inviteUser: jest.fn(),
97
- userInvited: jest.fn(),
98
- inviteError: jest.fn(),
99
- removeUser: jest.fn(),
100
- userRemoved: jest.fn(),
101
- removeUserError: jest.fn(),
102
- acceptInvite: jest.fn(),
103
- inviteAccepted: jest.fn(),
104
- acceptInviteError: jest.fn(),
105
- updateInvite: jest.fn(),
106
- inviteUpdated: jest.fn(),
107
- inviteUpdateError: jest.fn(),
108
- rejectInvite: jest.fn(),
109
- inviteRejected: jest.fn(),
110
- inviteRejectError: jest.fn(),
111
- },
104
+ publish: makeFns("creation", "created", "update", "updated", "delete", "deleted"),
105
+ subscribe: makeFns(
106
+ "creation", "created", "creationError",
107
+ "update", "updated", "updateError",
108
+ "delete", "deleted", "deletionError",
109
+ "createRegistry", "registryCreated", "registryCreationError",
110
+ "updateRegistry", "registryUpdated", "registryUpdateError",
111
+ "deleteRegistry", "registryDeleted", "registryDeletionError",
112
+ "inviteUser", "userInvited", "inviteError",
113
+ "removeUser", "userRemoved", "removeUserError",
114
+ "updateInvite", "inviteUpdated", "inviteUpdateError",
115
+ "acceptInvite", "inviteAccepted", "acceptInviteError",
116
+ "rejectInvite", "inviteRejected", "inviteRejectError",
117
+ "createToken", "tokenCreated", "tokenCreationError",
118
+ "deleteToken", "tokenDeleted", "tokenDeletionError",
119
+ ),
120
+ unsubscribe: makeFns(
121
+ "creation", "created", "creationError",
122
+ "update", "updated", "updateError",
123
+ "delete", "deleted", "deletionError",
124
+ "createRegistry", "registryCreated", "registryCreationError",
125
+ "updateRegistry", "registryUpdated", "registryUpdateError",
126
+ "deleteRegistry", "registryDeleted", "registryDeletionError",
127
+ "inviteUser", "userInvited", "inviteError",
128
+ "removeUser", "userRemoved", "removeUserError",
129
+ "updateInvite", "inviteUpdated", "inviteUpdateError",
130
+ "acceptInvite", "inviteAccepted", "acceptInviteError",
131
+ "rejectInvite", "inviteRejected", "inviteRejectError",
132
+ "createToken", "tokenCreated", "tokenCreationError",
133
+ "deleteToken", "tokenDeleted", "tokenDeletionError",
134
+ ),
112
135
  },
136
+
113
137
  user: {
114
- subscribe: {
115
- creation: jest.fn(),
116
- created: jest.fn(),
117
- creationError: jest.fn(),
118
- update: jest.fn(),
119
- updated: jest.fn(),
120
- updateError: jest.fn(),
121
- load: jest.fn(),
122
- loaded: jest.fn(),
123
- loadError: jest.fn(),
124
- delete: jest.fn(),
125
- deleted: jest.fn(),
126
- deletionError: jest.fn(),
127
- },
128
- unsubscribe: {
129
- creation: jest.fn(),
130
- created: jest.fn(),
131
- creationError: jest.fn(),
132
- update: jest.fn(),
133
- updated: jest.fn(),
134
- updateError: jest.fn(),
135
- load: jest.fn(),
136
- loaded: jest.fn(),
137
- loadError: jest.fn(),
138
- delete: jest.fn(),
139
- deleted: jest.fn(),
140
- deletionError: jest.fn(),
141
- },
138
+ publish: makeFns("creation", "load", "loaded", "update", "delete", "authError"),
139
+ subscribe: makeFns(
140
+ "creation", "created", "creationError",
141
+ "update", "updated", "updateError",
142
+ "load", "loaded", "loadError",
143
+ "delete", "deleted", "deletionError", "authError",
144
+ ),
145
+ unsubscribe: makeFns(
146
+ "creation", "created", "creationError",
147
+ "update", "updated", "updateError",
148
+ "load", "loaded", "loadError",
149
+ "delete", "deleted", "deletionError", "authError",
150
+ ),
142
151
  },
152
+
143
153
  account: {
144
- subscribe: {
145
- creation: jest.fn(),
146
- created: jest.fn(),
147
- creationError: jest.fn(),
148
- update: jest.fn(),
149
- updated: jest.fn(),
150
- updateError: jest.fn(),
151
- delete: jest.fn(),
152
- deleted: jest.fn(),
153
- deletionError: jest.fn(),
154
- clean: jest.fn(),
155
- cleaned: jest.fn(),
156
- cleanError: jest.fn(),
157
- },
158
- unsubscribe: {
159
- creation: jest.fn(),
160
- created: jest.fn(),
161
- creationError: jest.fn(),
162
- update: jest.fn(),
163
- updated: jest.fn(),
164
- updateError: jest.fn(),
165
- delete: jest.fn(),
166
- deleted: jest.fn(),
167
- deletionError: jest.fn(),
168
- clean: jest.fn(),
169
- cleaned: jest.fn(),
170
- cleanError: jest.fn(),
171
- },
154
+ publish: makeFns("creation", "created", "update", "delete"),
155
+ subscribe: makeFns(
156
+ "creation", "created", "creationError",
157
+ "update", "updated", "updateError",
158
+ "delete", "deleted", "deletionError",
159
+ "clean", "cleaned", "cleanError",
160
+ ),
161
+ unsubscribe: makeFns(
162
+ "creation", "created", "creationError",
163
+ "update", "updated", "updateError",
164
+ "delete", "deleted", "deletionError",
165
+ "clean", "cleaned", "cleanError",
166
+ ),
167
+ },
168
+
169
+ plan: {
170
+ publish: makeFns("upgrade", "upgraded", "downgrade", "downgraded"),
171
+ subscribe: makeFns("upgrade", "upgraded", "upgradeError", "downgrade", "downgraded", "downgradeError"),
172
+ unsubscribe: makeFns("upgrade", "upgraded", "upgradeError", "downgrade", "downgraded", "downgradeError"),
172
173
  },
174
+
173
175
  environment: {
174
- subscribe: {
175
- creation: jest.fn(),
176
- created: jest.fn(),
177
- creationError: jest.fn(),
178
- update: jest.fn(),
179
- updated: jest.fn(),
180
- updateError: jest.fn(),
181
- delete: jest.fn(),
182
- deleted: jest.fn(),
183
- deletionError: jest.fn(),
184
- clean: jest.fn(),
185
- cleaned: jest.fn(),
186
- cleanError: jest.fn(),
187
- scale: jest.fn(),
188
- scaled: jest.fn(),
189
- scaleError: jest.fn()
190
- },
191
- unsubscribe: {
192
- creation: jest.fn(),
193
- created: jest.fn(),
194
- creationError: jest.fn(),
195
- update: jest.fn(),
196
- updated: jest.fn(),
197
- updateError: jest.fn(),
198
- delete: jest.fn(),
199
- deleted: jest.fn(),
200
- deletionError: jest.fn(),
201
- clean: jest.fn(),
202
- cleaned: jest.fn(),
203
- cleanError: jest.fn(),
204
- scale: jest.fn(),
205
- scaled: jest.fn(),
206
- scaleError: jest.fn()
207
- },
176
+ publish: makeFns("creation", "created", "update", "delete", "clean", "scale"),
177
+ subscribe: makeFns(
178
+ "creation", "created", "creationError",
179
+ "update", "updated", "updateError",
180
+ "delete", "deleted", "deletionError",
181
+ "clean", "cleaned", "cleanError",
182
+ "scale", "scaled", "scaleError",
183
+ ),
184
+ unsubscribe: makeFns(
185
+ "creation", "created", "creationError",
186
+ "update", "updated", "updateError",
187
+ "delete", "deleted", "deletionError",
188
+ "clean", "cleaned", "cleanError",
189
+ "scale", "scaled", "scaleError",
190
+ ),
208
191
  },
192
+
209
193
  service: {
210
- subscribe: {
211
- deploy: jest.fn(),
212
- deployed: jest.fn(),
213
- deploymentError: jest.fn(),
214
- update: jest.fn(),
215
- updated: jest.fn(),
216
- updateError: jest.fn(),
217
- delete: jest.fn(),
218
- deleted: jest.fn(),
219
- deletionError: jest.fn(),
220
- requestLogs: jest.fn(),
221
- restart: jest.fn(),
222
- requestRevisionData: jest.fn(),
223
- updateServiceLinks: jest.fn(),
224
- changeRevision: jest.fn(),
225
- revisionChanged : jest.fn(),
226
- revisionChangeError: jest.fn(),
227
- },
228
- unsubscribe: {
229
- deploy: jest.fn(),
230
- deployed: jest.fn(),
231
- deploymentError: jest.fn(),
232
- update: jest.fn(),
233
- updated: jest.fn(),
234
- updateError: jest.fn(),
235
- delete: jest.fn(),
236
- deleted: jest.fn(),
237
- deletionError: jest.fn(),
238
- requestLogs: jest.fn(),
239
- requestRevisionData: jest.fn(),
240
- updateServiceLinks: jest.fn(),
241
- changeRevision: jest.fn(),
242
- revisionChanged : jest.fn(),
243
- revisionChangeError: jest.fn(),
244
- },
194
+ publish: makeFns("deploy", "deployed", "deploymentError", "update", "updated", "delete", "deleted", "restart"),
195
+ subscribe: makeFns(
196
+ "deploy", "deployed", "deploymentError",
197
+ "update", "updated", "updateError",
198
+ "delete", "deleted", "deletionError",
199
+ "requestLogs", "restart", "restarted", "restartError",
200
+ "requestRevisionData", "updateServiceLinks",
201
+ "changeRevision", "revisionChanged", "revisionChangeError",
202
+ "restartInstance", "instanceRestarted", "instanceRestartError",
203
+ ),
204
+ unsubscribe: makeFns(
205
+ "deploy", "deployed", "deploymentError",
206
+ "update", "updated", "updateError",
207
+ "delete", "deleted", "deletionError",
208
+ "requestLogs", "restart", "restarted", "restartError",
209
+ "requestRevisionData", "updateServiceLinks",
210
+ "changeRevision", "revisionChanged", "revisionChangeError",
211
+ "restartInstance", "instanceRestarted", "instanceRestartError",
212
+ ),
245
213
  },
214
+
246
215
  marketplace: {
247
- subscribe: {
248
- deployItem: jest.fn(),
249
- itemDeployed: jest.fn(),
250
- deploymentError: jest.fn(),
251
- updateItem: jest.fn(),
252
- itemUpdated: jest.fn(),
253
- updateError: jest.fn(),
254
- deleteItem: jest.fn(),
255
- itemDeleted: jest.fn(),
256
- deletionError: jest.fn(),
257
- loadItems: jest.fn(),
258
- },
259
- unsubscribe: {
260
- deployItem: jest.fn(),
261
- itemDeployed: jest.fn(),
262
- deploymentError: jest.fn(),
263
- updateItem: jest.fn(),
264
- itemUpdated: jest.fn(),
265
- updateError: jest.fn(),
266
- deleteItem: jest.fn(),
267
- itemDeleted: jest.fn(),
268
- deletionError: jest.fn(),
269
- loadItems: jest.fn(),
270
- },
216
+ publish: makeFns(
217
+ "deployItem", "itemDeployed", "deploymentError",
218
+ "updateItem", "itemUpdated", "updateError",
219
+ "deleteItem", "itemDeleted", "deletionError",
220
+ "loadItems", "itemsLoaded", "loadSchema",
221
+ "schemaLoaded", "schemaLoadError",
222
+ ),
223
+ subscribe: makeFns(
224
+ "deployItem", "itemDeployed", "deploymentError",
225
+ "updateItem", "itemUpdated", "updateError",
226
+ "deleteItem", "itemDeleted", "deletionError",
227
+ "loadItems", "itemsLoaded", "loadSchema",
228
+ "schemaLoaded", "schemaLoadError",
229
+ ),
230
+ unsubscribe: makeFns(
231
+ "deployItem", "itemDeployed", "deploymentError",
232
+ "updateItem", "itemUpdated", "updateError",
233
+ "deleteItem", "itemDeleted", "deletionError",
234
+ "loadItems", "itemsLoaded", "loadSchema",
235
+ "schemaLoaded", "schemaLoadError",
236
+ ),
271
237
  },
238
+
272
239
  resource: {
273
- subscribe: {
274
- creation: jest.fn(),
275
- created: jest.fn(),
276
- creationError: jest.fn(),
277
- update: jest.fn(),
278
- updated: jest.fn(),
279
- updateError: jest.fn(),
280
- delete: jest.fn(),
281
- deleted: jest.fn(),
282
- deletionError: jest.fn(),
283
- },
284
- unsubscribe: {
285
- creation: jest.fn(),
286
- created: jest.fn(),
287
- creationError: jest.fn(),
288
- update: jest.fn(),
289
- updated: jest.fn(),
290
- updateError: jest.fn(),
291
- delete: jest.fn(),
292
- deleted: jest.fn(),
293
- deletionError: jest.fn(),
294
- },
295
- },
296
- plan: {
297
- subscribe: {
298
- upgrade: jest.fn(),
299
- upgraded: jest.fn(),
300
- upgradeError: jest.fn(),
301
- downgrade: jest.fn(),
302
- downgraded: jest.fn(),
303
- downgradeError: jest.fn(),
304
- },
305
- unsubscribe: {
306
- upgrade: jest.fn(),
307
- upgraded: jest.fn(),
308
- upgradeError: jest.fn(),
309
- downgrade: jest.fn(),
310
- downgraded: jest.fn(),
311
- downgradeError: jest.fn(),
312
- },
240
+ publish: makeFns("creation", "created", "update", "delete", "deleted"),
241
+ subscribe: makeFns(
242
+ "creation", "created", "creationError",
243
+ "update", "updated", "updateError",
244
+ "delete", "deleted", "deletionError",
245
+ ),
246
+ unsubscribe: makeFns(
247
+ "creation", "created", "creationError",
248
+ "update", "updated", "updateError",
249
+ "delete", "deleted", "deletionError",
250
+ ),
313
251
  },
252
+
314
253
  organization: {
315
- subscribe: {
316
- creation: jest.fn(),
317
- created: jest.fn(),
318
- creationError: jest.fn(),
319
- update: jest.fn(),
320
- updated: jest.fn(),
321
- updateError: jest.fn(),
322
- delete: jest.fn(),
323
- deleted: jest.fn(),
324
- deletionError: jest.fn(),
325
- },
326
- unsubscribe: {
327
- creation: jest.fn(),
328
- created: jest.fn(),
329
- creationError: jest.fn(),
330
- update: jest.fn(),
331
- updated: jest.fn(),
332
- updateError: jest.fn(),
333
- delete: jest.fn(),
334
- deleted: jest.fn(),
335
- deletionError: jest.fn(),
336
- },
254
+ publish: makeFns("creation", "update", "delete"),
255
+ subscribe: makeFns(
256
+ "creation", "created", "creationError",
257
+ "update", "updated", "updateError",
258
+ "delete", "deleted", "deletionError",
259
+ ),
260
+ unsubscribe: makeFns(
261
+ "creation", "created", "creationError",
262
+ "update", "updated", "updateError",
263
+ "delete", "deleted", "deletionError",
264
+ ),
337
265
  },
266
+
338
267
  planProviders: {
339
- subscribe: {
340
- loadPlans: jest.fn(),
341
- plansLoaded: jest.fn(),
342
- loadError: jest.fn(),
343
- },
344
- unsubscribe: {
345
- loadPlans: jest.fn(),
346
- plansLoaded: jest.fn(),
347
- loadError: jest.fn(),
348
- },
268
+ publish: makeFns("loadPlans", "plansLoaded", "loadError"),
269
+ subscribe: makeFns("loadPlans", "plansLoaded", "loadError"),
270
+ unsubscribe: makeFns("loadPlans", "plansLoaded", "loadError"),
349
271
  },
350
272
  });
351
273
 
352
- describe("BackendHandler - Cobertura extendida", () => {
353
- let dummyGlobalEventHandler: ReturnType<typeof createDummyGlobalEventHandler>;
274
+ // ── Helper para construir y esperar que el constructor termine ────────────────
354
275
 
355
- beforeEach(() => {
356
- dummyGlobalEventHandler = createDummyGlobalEventHandler();
357
- jest.clearAllMocks();
358
- });
276
+ async function buildHandler(route = "home", handler?: ReturnType<typeof createDummyHandler>) {
277
+ const h = handler ?? createDummyHandler();
278
+ const bh = new BackendHandler(route, h, "http://localhost:9080", "v1");
279
+ await Promise.resolve(); // flush microtask queue (getUserHTTP().then)
280
+ return { bh, h };
281
+ }
359
282
 
360
- it("lanza error si globalEventHandler es inválido", () => {
361
- expect(() => {
362
- new BackendHandler("home", null, "httl://localhost:9080", "nightly");
363
- }).toThrow("globalEventHandler inválido");
364
- });
283
+ // ─────────────────────────────────────────────────────────────────────────────
284
+ // Tests
285
+ // ─────────────────────────────────────────────────────────────────────────────
365
286
 
366
- it("se suscribe al evento load de usuario en el constructor", () => {
367
- new BackendHandler(
368
- "user-information",
369
- dummyGlobalEventHandler,
370
- "httl://localhost:9080",
371
- "nightly"
287
+ describe("BackendHandler - constructor validation", () => {
288
+ it("lanza error si globalEventHandler es null", () => {
289
+ expect(() => new BackendHandler("home", null, "http://localhost", "v1")).toThrow(
290
+ "globalEventHandler inválido",
372
291
  );
373
- expect(dummyGlobalEventHandler.user.subscribe.load).toHaveBeenCalled();
374
292
  });
375
293
 
376
- it("se suscribe a eventos de tenant cuando la ruta es 'tenants' o 'create-tenant'", () => {
377
- new BackendHandler(
378
- "tenants",
379
- dummyGlobalEventHandler,
380
- "httl://localhost:9080",
381
- "nightly"
382
- );
383
- expect(
384
- dummyGlobalEventHandler.tenant.subscribe.creation
385
- ).toHaveBeenCalled();
386
- expect(dummyGlobalEventHandler.tenant.subscribe.created).toHaveBeenCalled();
387
- expect(
388
- dummyGlobalEventHandler.tenant.subscribe.deletionError
389
- ).toHaveBeenCalled();
390
- expect(
391
- dummyGlobalEventHandler.planProviders.subscribe.loadPlans
392
- ).toHaveBeenCalled();
393
- });
394
-
395
- it("se suscribe a eventos de usuario cuando la ruta es 'user-information' o 'home'", () => {
396
- new BackendHandler(
397
- "home",
398
- dummyGlobalEventHandler,
399
- "httl://localhost:9080",
400
- "nightly"
294
+ it("lanza error si globalEventHandler no tiene subscribe", () => {
295
+ expect(() => new BackendHandler("home", { publish: jest.fn() }, "http://localhost", "v1")).toThrow(
296
+ "globalEventHandler inválido",
401
297
  );
402
- expect(dummyGlobalEventHandler.user.subscribe.creation).toHaveBeenCalled();
403
- expect(dummyGlobalEventHandler.user.subscribe.load).toHaveBeenCalled();
404
298
  });
405
299
 
406
- it("se suscribe a eventos de account y plan cuando la ruta es 'accounts' o 'create-account'", () => {
407
- new BackendHandler(
408
- "accounts",
409
- dummyGlobalEventHandler,
410
- "httl://localhost:9080",
411
- "nightly"
412
- );
413
- expect(
414
- dummyGlobalEventHandler.account.subscribe.creation
415
- ).toHaveBeenCalled();
416
- expect(dummyGlobalEventHandler.plan.subscribe.upgrade).toHaveBeenCalled();
417
- });
418
-
419
- it("se suscribe a eventos de environment cuando la ruta es 'environments' o 'create-environment'", () => {
420
- new BackendHandler(
421
- "environments",
422
- dummyGlobalEventHandler,
423
- "httl://localhost:9080",
424
- "nightly"
425
- );
426
- expect(
427
- dummyGlobalEventHandler.environment.subscribe.creation
428
- ).toHaveBeenCalled();
300
+ it("no lanza error con handler válido", async () => {
301
+ const { h } = await buildHandler();
302
+ expect(h.user.subscribe.load).toHaveBeenCalled();
303
+ });
304
+ });
305
+
306
+ describe("BackendHandler - constructor suscripciones iniciales", () => {
307
+ it("suscribe a user.load y user.loaded en el constructor", async () => {
308
+ const { h } = await buildHandler();
309
+ expect(h.user.subscribe.load).toHaveBeenCalled();
310
+ expect(h.user.subscribe.loaded).toHaveBeenCalled();
311
+ });
312
+
313
+ it("publica user.load tras inicializar", async () => {
314
+ const { h } = await buildHandler();
315
+ expect(h.user.publish.load).toHaveBeenCalled();
316
+ });
317
+
318
+ it("invoca callback de user.load con initializeGlobalWebSocketClient", async () => {
319
+ const { initializeGlobalWebSocketClient } = await import("../websocket-manager");
320
+ const { h } = await buildHandler();
321
+ const loadCb = (h.user.subscribe.load as jest.Mock).mock.calls[0][0];
322
+ loadCb({} as UserData);
323
+ expect(initializeGlobalWebSocketClient).toHaveBeenCalled();
324
+ });
325
+
326
+ it("invoca callback de user.loaded con updateUserComplete", async () => {
327
+ const { updateUserComplete } = await import("../websocket-manager");
328
+ const { h } = await buildHandler();
329
+ const loadedCb = (h.user.subscribe.loaded as jest.Mock).mock.calls[0][0];
330
+ loadedCb({ id: "u1" } as any);
331
+ expect(updateUserComplete).toHaveBeenCalledWith({ id: "u1" });
332
+ });
333
+
334
+ it("llama a subscribeForRoute en el constructor", async () => {
335
+ const { h } = await buildHandler("home");
336
+ expect(h.tenant.subscribe.creation).toHaveBeenCalled();
337
+ expect(h.user.subscribe.creation).toHaveBeenCalled();
338
+ expect(h.account.subscribe.creation).toHaveBeenCalled();
339
+ expect(h.environment.subscribe.creation).toHaveBeenCalled();
340
+ expect(h.service.subscribe.deploy).toHaveBeenCalled();
341
+ expect(h.marketplace.subscribe.deployItem).toHaveBeenCalled();
342
+ expect(h.resource.subscribe.creation).toHaveBeenCalled();
343
+ expect(h.organization.subscribe.creation).toHaveBeenCalled();
344
+ expect(h.plan.subscribe.upgrade).toHaveBeenCalled();
345
+ expect(h.planProviders.subscribe.loadPlans).toHaveBeenCalled();
346
+ });
347
+ });
348
+
349
+ describe("BackendHandler - isUserLoggedIn", () => {
350
+ it("retorna true cuando isUserLogged devuelve true", async () => {
351
+ const { isUserLogged } = await import("../api/user-api-service");
352
+ (isUserLogged as jest.Mock).mockResolvedValue(true);
353
+ const { bh } = await buildHandler();
354
+ const result = await bh.isUserLoggedIn();
355
+ expect(result).toBe(true);
356
+ });
357
+
358
+ it("retorna false cuando isUserLogged devuelve false", async () => {
359
+ const { isUserLogged } = await import("../api/user-api-service");
360
+ (isUserLogged as jest.Mock).mockResolvedValue(false);
361
+ const { bh } = await buildHandler();
362
+ const result = await bh.isUserLoggedIn();
363
+ expect(result).toBe(false);
364
+ });
365
+
366
+ it("retorna false cuando isUserLogged devuelve string", async () => {
367
+ const { isUserLogged } = await import("../api/user-api-service");
368
+ (isUserLogged as jest.Mock).mockResolvedValue("yes");
369
+ const { bh } = await buildHandler();
370
+ const result = await bh.isUserLoggedIn();
371
+ expect(result).toBe(false);
372
+ });
373
+ });
374
+
375
+ describe("BackendHandler - changeRoute y unsubscribe", () => {
376
+ it("changeRoute desuscribe eventos anteriores y suscribe nuevos", async () => {
377
+ const { bh, h } = await buildHandler("home");
378
+ const firstCallCount = (h.tenant.subscribe.creation as jest.Mock).mock.calls.length;
379
+ bh.changeRoute("tenants");
380
+ await Promise.resolve();
381
+ expect(h.tenant.unsubscribe.creation).toHaveBeenCalled();
382
+ expect((h.tenant.subscribe.creation as jest.Mock).mock.calls.length).toBeGreaterThan(firstCallCount);
383
+ });
384
+
385
+ it("changeRoute múltiple acumula y limpia correctamente", async () => {
386
+ const { bh, h } = await buildHandler("home");
387
+ bh.changeRoute("accounts");
388
+ bh.changeRoute("environments");
389
+ expect(h.account.unsubscribe.creation).toHaveBeenCalled();
390
+ });
391
+ });
392
+
393
+ // ── Callbacks de Tenant ───────────────────────────────────────────────────────
394
+
395
+ describe("BackendHandler - callbacks de Tenant", () => {
396
+ it("creation callback llama a createTenant", async () => {
397
+ const { createTenant } = await import("../api/tenant-api-service");
398
+ const { h } = await buildHandler();
399
+ const cb = (h.tenant.subscribe.creation as jest.Mock).mock.calls[0][0];
400
+ const tenant = { id: "t1", name: "T1" } as Tenant;
401
+ cb(tenant);
402
+ expect(createTenant).toHaveBeenCalledWith(tenant, "");
403
+ });
404
+
405
+ it("update callback llama a updateTenant", async () => {
406
+ const { updateTenant } = await import("../api/tenant-api-service");
407
+ const { h } = await buildHandler();
408
+ const cb = (h.tenant.subscribe.update as jest.Mock).mock.calls[0][0];
409
+ const tenant = { id: "t1" } as Tenant;
410
+ cb(tenant);
411
+ expect(updateTenant).toHaveBeenCalledWith(tenant, "");
412
+ });
413
+
414
+ it("delete callback llama a deleteTenant", async () => {
415
+ const { deleteTenant } = await import("../api/tenant-api-service");
416
+ const { h } = await buildHandler();
417
+ const cb = (h.tenant.subscribe.delete as jest.Mock).mock.calls[0][0];
418
+ cb({ id: "t1" } as Tenant);
419
+ expect(deleteTenant).toHaveBeenCalled();
420
+ });
421
+
422
+ it("createRegistry callback llama a createRegistry", async () => {
423
+ const { createRegistry } = await import("../api/tenant-api-service");
424
+ const { h } = await buildHandler();
425
+ const cb = (h.tenant.subscribe.createRegistry as jest.Mock).mock.calls[0][0];
426
+ cb({ tenant: { id: "t1" } as Tenant, registry: { name: "reg1" } as any });
427
+ expect(createRegistry).toHaveBeenCalled();
428
+ });
429
+
430
+ it("updateRegistry callback llama a updateRegistry", async () => {
431
+ const { updateRegistry } = await import("../api/tenant-api-service");
432
+ const { h } = await buildHandler();
433
+ const cb = (h.tenant.subscribe.updateRegistry as jest.Mock).mock.calls[0][0];
434
+ cb({ tenant: { id: "t1" } as Tenant, registry: { name: "reg1" } as any });
435
+ expect(updateRegistry).toHaveBeenCalled();
436
+ });
437
+
438
+ it("deleteRegistry callback llama a deleteRegistry", async () => {
439
+ const { deleteRegistry } = await import("../api/tenant-api-service");
440
+ const { h } = await buildHandler();
441
+ const cb = (h.tenant.subscribe.deleteRegistry as jest.Mock).mock.calls[0][0];
442
+ cb({ tenant: { id: "t1" } as Tenant, registry: { name: "reg1" } as any });
443
+ expect(deleteRegistry).toHaveBeenCalled();
444
+ });
445
+
446
+ it("inviteUser callback llama a inviteUser", async () => {
447
+ const { inviteUser } = await import("../api/tenant-api-service");
448
+ const { h } = await buildHandler();
449
+ const cb = (h.tenant.subscribe.inviteUser as jest.Mock).mock.calls[0][0];
450
+ cb({ tenant: "t1", user: "u1", role: "admin" as any });
451
+ expect(inviteUser).toHaveBeenCalled();
452
+ });
453
+
454
+ it("userInvited callback llama a updateUserRole", async () => {
455
+ const { updateUserRole } = await import("../api/tenant-api-service");
456
+ const { h } = await buildHandler();
457
+ const cb = (h.tenant.subscribe.userInvited as jest.Mock).mock.calls[0][0];
458
+ cb({ user: "u1", tenant: "t1", role: "member" as any });
459
+ expect(updateUserRole).toHaveBeenCalled();
460
+ });
461
+
462
+ it("removeUser callback llama a removeUser", async () => {
463
+ const { removeUser } = await import("../api/tenant-api-service");
464
+ const { h } = await buildHandler();
465
+ const cb = (h.tenant.subscribe.removeUser as jest.Mock).mock.calls[0][0];
466
+ cb({ tenant: "t1", user: "u1" });
467
+ expect(removeUser).toHaveBeenCalled();
468
+ });
469
+
470
+ it("updateInvite callback llama a updateUserRole", async () => {
471
+ const { updateUserRole } = await import("../api/tenant-api-service");
472
+ const { h } = await buildHandler();
473
+ const cb = (h.tenant.subscribe.updateInvite as jest.Mock).mock.calls[0][0];
474
+ cb({ user: "u1", tenant: "t1", role: "member" as any });
475
+ expect(updateUserRole).toHaveBeenCalled();
476
+ });
477
+
478
+ it("acceptInvite callback llama a acceptInvite", async () => {
479
+ const { acceptInvite } = await import("../api/tenant-api-service");
480
+ const { h } = await buildHandler();
481
+ const cb = (h.tenant.subscribe.acceptInvite as jest.Mock).mock.calls[0][0];
482
+ cb({ tenant: "t1" });
483
+ expect(acceptInvite).toHaveBeenCalledWith("t1", "");
484
+ });
485
+
486
+ it("rejectInvite callback llama a rejectInvite", async () => {
487
+ const { rejectInvite } = await import("../api/tenant-api-service");
488
+ const { h } = await buildHandler();
489
+ const cb = (h.tenant.subscribe.rejectInvite as jest.Mock).mock.calls[0][0];
490
+ cb({ tenant: "t1", leave: true });
491
+ expect(rejectInvite).toHaveBeenCalledWith("t1", "", true);
492
+ });
493
+
494
+ it("createToken callback llama a createToken", async () => {
495
+ const { createToken } = await import("../api/tenant-api-service");
496
+ const { h } = await buildHandler();
497
+ const cb = (h.tenant.subscribe.createToken as jest.Mock).mock.calls[0][0];
498
+ cb({ tenant: "t1", expiration: "2025-01-01", description: "desc" });
499
+ expect(createToken).toHaveBeenCalled();
500
+ });
501
+
502
+ it("deleteToken callback llama a deleteToken", async () => {
503
+ const { deleteToken } = await import("../api/tenant-api-service");
504
+ const { h } = await buildHandler();
505
+ const cb = (h.tenant.subscribe.deleteToken as jest.Mock).mock.calls[0][0];
506
+ cb({ tenant: "t1", token: "tok123" });
507
+ expect(deleteToken).toHaveBeenCalledWith("t1", "tok123", "");
508
+ });
509
+
510
+ it("loadPlans callback llama a getPlanProviders", async () => {
511
+ const { getPlanProviders } = await import("../api/planProvider-api-service");
512
+ const { h } = await buildHandler();
513
+ const cb = (h.planProviders.subscribe.loadPlans as jest.Mock).mock.calls[0][0];
514
+ cb();
515
+ expect(getPlanProviders).toHaveBeenCalled();
516
+ });
517
+
518
+ it("empty callbacks (created, creationError, etc.) no lanzan error", async () => {
519
+ const { h } = await buildHandler();
520
+ const created = (h.tenant.subscribe.created as jest.Mock).mock.calls[0][0];
521
+ const creationError = (h.tenant.subscribe.creationError as jest.Mock).mock.calls[0][0];
522
+ const updated = (h.tenant.subscribe.updated as jest.Mock).mock.calls[0][0];
523
+ const updateError = (h.tenant.subscribe.updateError as jest.Mock).mock.calls[0][0];
524
+ const deleted = (h.tenant.subscribe.deleted as jest.Mock).mock.calls[0][0];
525
+ const deletionError = (h.tenant.subscribe.deletionError as jest.Mock).mock.calls[0][0];
526
+ expect(() => created({})).not.toThrow();
527
+ expect(() => creationError({})).not.toThrow();
528
+ expect(() => updated({})).not.toThrow();
529
+ expect(() => updateError({})).not.toThrow();
530
+ expect(() => deleted({})).not.toThrow();
531
+ expect(() => deletionError({})).not.toThrow();
429
532
  });
533
+ });
534
+
535
+ // ── Callbacks de User ─────────────────────────────────────────────────────────
536
+
537
+ describe("BackendHandler - callbacks de User", () => {
538
+ it("creation callback llama a createUser", async () => {
539
+ const { createUser } = await import("../api/user-api-service");
540
+ const { h } = await buildHandler();
541
+ const cb = (h.user.subscribe.creation as jest.Mock).mock.calls[0][0];
542
+ cb({} as UserData);
543
+ expect(createUser).toHaveBeenCalled();
544
+ });
545
+
546
+ it("update callback llama a updateUser", async () => {
547
+ const { updateUser } = await import("../api/user-api-service");
548
+ const { h } = await buildHandler();
549
+ const cb = (h.user.subscribe.update as jest.Mock).mock.calls[0][0];
550
+ cb({} as UserData);
551
+ expect(updateUser).toHaveBeenCalled();
552
+ });
553
+
554
+ it("load callback llama a loadUser", async () => {
555
+ const { loadUser } = await import("../api/user-api-service");
556
+ const { h } = await buildHandler();
557
+ // subscribeUserEvents registra su propio load callback (segundo llamado a subscribe.load)
558
+ const allCalls = (h.user.subscribe.load as jest.Mock).mock.calls;
559
+ const userEventsCb = allCalls[allCalls.length - 1][0];
560
+ userEventsCb({} as UserData);
561
+ expect(loadUser).toHaveBeenCalled();
562
+ });
563
+
564
+ it("delete callback llama a deleteUSer", async () => {
565
+ const { deleteUSer } = await import("../api/user-api-service");
566
+ const { h } = await buildHandler();
567
+ const cb = (h.user.subscribe.delete as jest.Mock).mock.calls[0][0];
568
+ cb({} as UserData);
569
+ expect(deleteUSer).toHaveBeenCalled();
570
+ });
571
+ });
572
+
573
+ // ── Callbacks de Account ──────────────────────────────────────────────────────
574
+
575
+ describe("BackendHandler - callbacks de Account", () => {
576
+ it("creation callback llama a createAccount", async () => {
577
+ const { createAccount } = await import("../api/account-api-service");
578
+ const { h } = await buildHandler();
579
+ const cb = (h.account.subscribe.creation as jest.Mock).mock.calls[0][0];
580
+ cb({ id: "a1" } as Account);
581
+ expect(createAccount).toHaveBeenCalled();
582
+ });
583
+
584
+ it("update callback llama a updateAccount", async () => {
585
+ const { updateAccount } = await import("../api/account-api-service");
586
+ const { h } = await buildHandler();
587
+ const cb = (h.account.subscribe.update as jest.Mock).mock.calls[0][0];
588
+ cb({ id: "a1" } as Account);
589
+ expect(updateAccount).toHaveBeenCalled();
590
+ });
591
+
592
+ it("delete callback llama a deleteAccount", async () => {
593
+ const { deleteAccount } = await import("../api/account-api-service");
594
+ const { h } = await buildHandler();
595
+ const cb = (h.account.subscribe.delete as jest.Mock).mock.calls[0][0];
596
+ cb({ id: "a1" } as Account);
597
+ expect(deleteAccount).toHaveBeenCalled();
598
+ });
599
+
600
+ it("clean callback llama a clearAccount", async () => {
601
+ const { clearAccount } = await import("../api/account-api-service");
602
+ const { h } = await buildHandler();
603
+ const cb = (h.account.subscribe.clean as jest.Mock).mock.calls[0][0];
604
+ cb({ id: "a1" } as Account);
605
+ expect(clearAccount).toHaveBeenCalled();
606
+ });
607
+ });
430
608
 
431
- it("se suscribe a eventos de service cuando la ruta es 'deploy-service'", () => {
432
- new BackendHandler(
433
- "deploy-service",
434
- dummyGlobalEventHandler,
435
- "httl://localhost:9080",
436
- "nightly"
609
+ // ── Callbacks de Plan ─────────────────────────────────────────────────────────
610
+
611
+ describe("BackendHandler - callbacks de Plan", () => {
612
+ it("upgraded callback publica notificación de plan-upgrade", async () => {
613
+ const { h } = await buildHandler();
614
+ const cb = (h.plan.subscribe.upgraded as jest.Mock).mock.calls[0][0];
615
+ cb("premium");
616
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
617
+ expect.objectContaining({ type: "success", subtype: "plan-upgrade", data: { plan: "premium" } }),
437
618
  );
438
- expect(dummyGlobalEventHandler.service.subscribe.deploy).toHaveBeenCalled();
439
619
  });
440
620
 
441
- it("se suscribe a eventos de marketplace para deployItem y loadItems", () => {
442
- new BackendHandler(
443
- "deploy-marketplace",
444
- dummyGlobalEventHandler,
445
- "httl://localhost:9080",
446
- "nightly"
621
+ it("downgraded callback publica notificación de plan-downgrade", async () => {
622
+ const { h } = await buildHandler();
623
+ const cb = (h.plan.subscribe.downgraded as jest.Mock).mock.calls[0][0];
624
+ cb("freemium");
625
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
626
+ expect.objectContaining({ type: "success", subtype: "plan-downgrade", data: { plan: "freemium" } }),
447
627
  );
448
- expect(
449
- dummyGlobalEventHandler.marketplace.subscribe.deployItem
450
- ).toHaveBeenCalled();
451
-
452
- new BackendHandler(
453
- "marketplace",
454
- dummyGlobalEventHandler,
455
- "httl://localhost:9080",
456
- "nightly"
628
+ });
629
+
630
+ it("upgrade y downgrade empty callbacks no lanzan error", async () => {
631
+ const { h } = await buildHandler();
632
+ const upgradeCb = (h.plan.subscribe.upgrade as jest.Mock).mock.calls[0][0];
633
+ const upgradeErrorCb = (h.plan.subscribe.upgradeError as jest.Mock).mock.calls[0][0];
634
+ const downgradeCb = (h.plan.subscribe.downgrade as jest.Mock).mock.calls[0][0];
635
+ const downgradeErrorCb = (h.plan.subscribe.downgradeError as jest.Mock).mock.calls[0][0];
636
+ expect(() => upgradeCb("p")).not.toThrow();
637
+ expect(() => upgradeErrorCb("p")).not.toThrow();
638
+ expect(() => downgradeCb("p")).not.toThrow();
639
+ expect(() => downgradeErrorCb("p")).not.toThrow();
640
+ });
641
+ });
642
+
643
+ // ── Callbacks de Environment ──────────────────────────────────────────────────
644
+
645
+ describe("BackendHandler - callbacks de Environment", () => {
646
+ const mockEnv: Environment = {
647
+ id: "e1",
648
+ name: "Env1",
649
+ account: "acc1",
650
+ tenant: "ten1",
651
+ logo: "",
652
+ services: [],
653
+ domains: [],
654
+ status: { code: "", message: "", timestamp: "" },
655
+ usage: {
656
+ current: { cpu: 0, memory: 0, storage: 0, volatileStorage: 0, nonReplicatedStorage: 0, persistentStorage: 0 },
657
+ limit: {
658
+ cpu: { max: 0, min: 0 }, memory: { max: 0, min: 0 }, storage: { max: 0, min: 0 },
659
+ volatileStorage: { max: 0, min: 0 }, nonReplicatedStorage: { max: 0, min: 0 }, persistentStorage: { max: 0, min: 0 },
660
+ },
661
+ cost: 0,
662
+ },
663
+ type: "",
664
+ cluster: { name: "" },
665
+ };
666
+
667
+ it("creation callback llama a createEnvironment", async () => {
668
+ const { createEnvironment } = await import("../api/environment-api-service");
669
+ const { h } = await buildHandler();
670
+ const cb = (h.environment.subscribe.creation as jest.Mock).mock.calls[0][0];
671
+ cb(mockEnv);
672
+ expect(createEnvironment).toHaveBeenCalled();
673
+ });
674
+
675
+ it("created callback publica notificación environment-created", async () => {
676
+ const { h } = await buildHandler();
677
+ const cb = (h.environment.subscribe.created as jest.Mock).mock.calls[0][0];
678
+ cb(mockEnv);
679
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
680
+ expect.objectContaining({ type: "success", subtype: "environment-created", data: { environment: "Env1", account: "acc1", tenant: "ten1" } }),
457
681
  );
458
- expect(
459
- dummyGlobalEventHandler.marketplace.subscribe.loadItems
460
- ).toHaveBeenCalled();
461
682
  });
462
683
 
463
- it("se suscribe a eventos de resource cuando la ruta es 'resources'", () => {
464
- new BackendHandler(
465
- "resources",
466
- dummyGlobalEventHandler,
467
- "httl://localhost:9080",
468
- "nightly"
684
+ it("update callback llama a updateEnvironment", async () => {
685
+ const { updateEnvironment } = await import("../api/environment-api-service");
686
+ const { h } = await buildHandler();
687
+ const cb = (h.environment.subscribe.update as jest.Mock).mock.calls[0][0];
688
+ cb(mockEnv);
689
+ expect(updateEnvironment).toHaveBeenCalled();
690
+ });
691
+
692
+ it("delete callback llama a deleteEnvironment", async () => {
693
+ const { deleteEnvironment } = await import("../api/environment-api-service");
694
+ const { h } = await buildHandler();
695
+ const cb = (h.environment.subscribe.delete as jest.Mock).mock.calls[0][0];
696
+ cb(mockEnv);
697
+ expect(deleteEnvironment).toHaveBeenCalled();
698
+ });
699
+
700
+ it("clean callback llama a clearEnvironment", async () => {
701
+ const { clearEnvironment } = await import("../api/environment-api-service");
702
+ const { h } = await buildHandler();
703
+ const cb = (h.environment.subscribe.clean as jest.Mock).mock.calls[0][0];
704
+ cb(mockEnv);
705
+ expect(clearEnvironment).toHaveBeenCalled();
706
+ });
707
+
708
+ it("scale callback llama a scaleEnvironment", async () => {
709
+ const { scaleEnvironment } = await import("../api/environment-api-service");
710
+ const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {});
711
+ const { h } = await buildHandler();
712
+ const cb = (h.environment.subscribe.scale as jest.Mock).mock.calls[0][0];
713
+ cb(mockEnv);
714
+ expect(scaleEnvironment).toHaveBeenCalled();
715
+ consoleSpy.mockRestore();
716
+ });
717
+ });
718
+
719
+ // ── Callbacks de Service ──────────────────────────────────────────────────────
720
+
721
+ describe("BackendHandler - callbacks de Service", () => {
722
+ const mockService: Service = {
723
+ id: "s1", tenant: "ten1", account: "acc1", environment: "env1",
724
+ name: "Svc1", logo: "", description: "", revisions: [], status: "",
725
+ role: [{ name: "r1", instances: [] }], links: [], resources: [], parameters: [],
726
+ usage: {
727
+ current: { cpu: 0, memory: 0, storage: 0, volatileStorage: 0, nonReplicatedStorage: 0, persistentStorage: 0 },
728
+ limit: {
729
+ cpu: { max: 0, min: 0 }, memory: { max: 0, min: 0 }, storage: { max: 0, min: 0 },
730
+ volatileStorage: { max: 0, min: 0 }, nonReplicatedStorage: { max: 0, min: 0 }, persistentStorage: { max: 0, min: 0 },
731
+ },
732
+ cost: 0,
733
+ },
734
+ project: "", registry: "", imageName: "", entrypoint: "", cmd: "",
735
+ serverChannels: [], clientChannels: [], duplexChannels: [], cloudProvider: "",
736
+ };
737
+
738
+ it("deployed callback publica notificación deployment-success", async () => {
739
+ const { h } = await buildHandler();
740
+ const cb = (h.service.subscribe.deployed as jest.Mock).mock.calls[0][0];
741
+ cb(mockService);
742
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
743
+ expect.objectContaining({ type: "success", subtype: "deployment-success" }),
469
744
  );
470
- expect(
471
- dummyGlobalEventHandler.resource.subscribe.creation
472
- ).toHaveBeenCalled();
473
- });
474
-
475
- // it("ejecuta unsubscribeAll al cambiar de ruta", () => {
476
- // const handler = new BackendHandler(
477
- // "tenants",
478
- // dummyGlobalEventHandler,
479
- // "httl://localhost:9080",
480
- // "nightly"
481
- // );
482
- // const unsubscribeMock = jest.fn();
483
- // (handler as any).unsubscribeFunctions.push(unsubscribeMock);
484
- // handler.changeRoute("user-information");
485
- // expect(unsubscribeMock).toHaveBeenCalled();
486
- // expect(dummyGlobalEventHandler.user.subscribe.creation).toHaveBeenCalled();
487
- // });
488
-
489
- it("dispara los callbacks de API al invocar los métodos internos", () => {
490
- const tenantCbSpy = jest
491
- .spyOn(BackendHandler.prototype as any, "subscribeTenantEvents")
492
- .mockImplementation(function () {
493
- createTenant(
494
- {
495
- id: "t1",
496
- name: "Tenant 1",
497
- organizationsIds: [],
498
- services: [],
499
- accounts: [],
500
- environments: [],
501
- marketplaceItems: [],
502
- resources: [],
503
- role: "",
504
- status: "",
505
- users: [],
506
- registry: [],
507
- } as Tenant,
508
- ""
509
- );
510
- });
511
- const testUserData: UserData = {
512
- id: "",
513
- name: "",
514
- surname: "",
515
- provider: [
516
- {
517
- name: "",
518
- email: "",
519
- password: "",
520
- },
521
- ],
522
- notificationsEnabled: "",
523
- organizations: [],
524
- clusterTokens: [],
525
- tokens: [],
526
- tenants: [],
527
- axebowPlan: "freemium",
528
- companyName: "",
529
- rol: "",
530
- };
531
- const userCbSpy = jest
532
- .spyOn(BackendHandler.prototype as any, "subscribeUserEvents")
533
- .mockImplementation(function () {
534
- loadUser("", testUserData);
535
- });
536
- const accountCbSpy = jest
537
- .spyOn(BackendHandler.prototype as any, "subscribeAccountEvents")
538
- .mockImplementation(function () {
539
- createAccount(
540
- {
541
- id: "a1",
542
- name: "Account 1",
543
- tenant: "",
544
- cloudProvider: { name: "" },
545
- logo: "",
546
- environments: [],
547
- services: [],
548
- domains: [],
549
- status: "",
550
- usage: {
551
- current: {
552
- cpu: 0,
553
- memory: 0,
554
- storage: 0,
555
- volatileStorage: 0,
556
- nonReplicatedStorage: 0,
557
- persistentStorage: 0,
558
- },
559
- limit: {
560
- cpu: { max: 0, min: 0 },
561
- memory: { max: 0, min: 0 },
562
- storage: { max: 0, min: 0 },
563
- volatileStorage: { max: 0, min: 0 },
564
- nonReplicatedStorage: { max: 0, min: 0 },
565
- persistentStorage: { max: 0, min: 0 },
566
- },
567
- cost: 0,
568
- },
569
- } as Account,
570
- ""
571
- );
572
- });
573
- const envCbSpy = jest
574
- .spyOn(BackendHandler.prototype as any, "subscribeEnvironmentEvents")
575
- .mockImplementation(function () {
576
- createEnvironment(
577
- {
578
- id: "e1",
579
- name: "Env 1",
580
- account: "",
581
- tenant: "",
582
- logo: "",
583
- services: [],
584
- domains: [],
585
- status: {
586
- code:"",
587
- message: "",
588
- timestamp: ""
589
- },
590
- usage: {
591
- current: {
592
- cpu: 0,
593
- memory: 0,
594
- storage: 0,
595
- volatileStorage: 0,
596
- nonReplicatedStorage: 0,
597
- persistentStorage: 0,
598
- },
599
- limit: {
600
- cpu: { max: 0, min: 0 },
601
- memory: { max: 0, min: 0 },
602
- storage: { max: 0, min: 0 },
603
- volatileStorage: { max: 0, min: 0 },
604
- nonReplicatedStorage: { max: 0, min: 0 },
605
- persistentStorage: { max: 0, min: 0 },
606
- },
607
- cost: 0,
608
- },
609
- type: "",
610
- cluster: { name: "" },
611
- } as Environment,
612
- ""
613
- );
614
- });
615
- const serviceCbSpy = jest
616
- .spyOn(BackendHandler.prototype as any, "subscribeServiceEvents")
617
- .mockImplementation(function () {
618
- deployService(
619
- {
620
- id: "s1",
621
- tenant: "",
622
- account: "",
623
- environment: "",
624
- name: "Service 1",
625
- logo: "",
626
- description: "",
627
- revisions: [],
628
- status: "",
629
- role: [{ name: "", instances: [] }],
630
- links: [],
631
- resources: [],
632
- parameters: [],
633
- usage: {
634
- current: {
635
- cpu: 0,
636
- memory: 0,
637
- storage: 0,
638
- volatileStorage: 0,
639
- nonReplicatedStorage: 0,
640
- persistentStorage: 0,
641
- },
642
- limit: {
643
- cpu: { max: 0, min: 0 },
644
- memory: { max: 0, min: 0 },
645
- storage: { max: 0, min: 0 },
646
- volatileStorage: { max: 0, min: 0 },
647
- nonReplicatedStorage: { max: 0, min: 0 },
648
- persistentStorage: { max: 0, min: 0 },
649
- },
650
- cost: 0,
651
- },
652
- project: "",
653
- registry: "",
654
- imageName: "",
655
- entrypoint: "",
656
- cmd: "",
657
- serverChannels: [],
658
- clientChannels: [],
659
- duplexChannels: [],
660
- cloudProvider: "",
661
- } as Service,
662
- ""
663
- );
664
- });
665
- const marketplaceDeploySpy = jest
666
- .spyOn(BackendHandler.prototype as any, "subscribeMarketplaceEvents")
667
- .mockImplementation(function () {
668
- deployMarketplaceItem({
669
- deploymentData: {
670
- id: "m1",
671
- tenant: "",
672
- account: "",
673
- environment: "",
674
- name: "Market 1",
675
- logo: "",
676
- description: "",
677
- revisions: [],
678
- status: "",
679
- role: [{ name: "", instances: [] }],
680
- links: [],
681
- resources: [],
682
- parameters: [],
683
- usage: {
684
- current: {
685
- cpu: 0,
686
- memory: 0,
687
- storage: 0,
688
- volatileStorage: 0,
689
- nonReplicatedStorage: 0,
690
- persistentStorage: 0,
691
- },
692
- limit: {
693
- cpu: { max: 0, min: 0 },
694
- memory: { max: 0, min: 0 },
695
- storage: { max: 0, min: 0 },
696
- volatileStorage: { max: 0, min: 0 },
697
- nonReplicatedStorage: { max: 0, min: 0 },
698
- persistentStorage: { max: 0, min: 0 },
699
- },
700
- cost: 0,
701
- },
702
- project: "",
703
- registry: "",
704
- imageName: "",
705
- entrypoint: "",
706
- cmd: "",
707
- serverChannels: [],
708
- clientChannels: [],
709
- duplexChannels: [],
710
- cloudProvider: "",
711
- },
712
- step: 0,
713
- selectedAccount: "",
714
- step1collapsed: false,
715
- step2collapsed: false,
716
- step3collapsed: false,
717
- step4collapsed: false,
718
- defaultExecutableCollapsed: false,
719
- autoScallingCollapsed: false,
720
- variableCreation: "",
721
- variableName: "",
722
- variableValue: "",
723
- variableList: [{ name: "", value: "", type: "" }],
724
- openCreationPopup: false,
725
- domain: "",
726
- module: "",
727
- version: "",
728
- resourceList: [{ name: "", type: "", required: false }],
729
- parameterList: [{ name: "", type: "", required: false }],
730
- serviceName: "",
731
- type: "",
732
- artifactName: "",
733
- } as MarketplaceService);
734
- });
735
- const marketplaceLoadSpy = jest
736
- .spyOn(BackendHandler.prototype as any, "subscribeMarketplaceEvents")
737
- .mockImplementation(function () {
738
- getMarketplaceItems(["t1", "t2"], "");
739
- });
740
-
741
- new BackendHandler(
742
- "tenants",
743
- dummyGlobalEventHandler,
744
- "httl://localhost:9080",
745
- "nightly"
745
+ });
746
+
747
+ it("deploymentError callback publica notificación deployment-error con error info", async () => {
748
+ const { h } = await buildHandler();
749
+ const cb = (h.service.subscribe.deploymentError as jest.Mock).mock.calls[0][0];
750
+ const svcWithErr = { ...mockService, error: { code: "500", message: "err", timestamp: "now" } };
751
+ cb(svcWithErr);
752
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
753
+ expect.objectContaining({ type: "error", subtype: "deployment-error", info_content: { code: "500", message: "err", timestamp: "now" } }),
746
754
  );
747
- new BackendHandler(
748
- "user-information",
749
- dummyGlobalEventHandler,
750
- "httl://localhost:9080",
751
- "nightly"
755
+ });
756
+
757
+ it("deploymentError callback maneja service sin error (defaults vacíos)", async () => {
758
+ const { h } = await buildHandler();
759
+ const cb = (h.service.subscribe.deploymentError as jest.Mock).mock.calls[0][0];
760
+ cb(mockService);
761
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
762
+ expect.objectContaining({ info_content: { code: "", message: "", timestamp: "" } }),
752
763
  );
753
- new BackendHandler(
754
- "accounts",
755
- dummyGlobalEventHandler,
756
- "httl://localhost:9080",
757
- "nightly"
764
+ });
765
+
766
+ it("deploy callback llama a deployService y publica notification cuando download es falsy", async () => {
767
+ const { deployService } = await import("../api/service-api-service");
768
+ const { h } = await buildHandler();
769
+ const cb = (h.service.subscribe.deploy as jest.Mock).mock.calls[0][0];
770
+ await cb({ ...mockService, download: false });
771
+ expect(deployService).toHaveBeenCalled();
772
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
773
+ expect.objectContaining({ type: "info", subtype: "deployment-in-progress" }),
758
774
  );
759
- new BackendHandler(
760
- "environments",
761
- dummyGlobalEventHandler,
762
- "httl://localhost:9080",
763
- "nightly"
775
+ });
776
+
777
+ it("deploy callback no publica notificación cuando download es true", async () => {
778
+ const { h } = await buildHandler();
779
+ const cb = (h.service.subscribe.deploy as jest.Mock).mock.calls[0][0];
780
+ (h.notification.publish.creation as jest.Mock).mockClear();
781
+ await cb({ ...mockService, download: true });
782
+ expect(h.notification.publish.creation).not.toHaveBeenCalled();
783
+ });
784
+
785
+ it("update callback llama a updateService", async () => {
786
+ const { updateService } = await import("../api/service-api-service");
787
+ const { h } = await buildHandler();
788
+ const cb = (h.service.subscribe.update as jest.Mock).mock.calls[0][0];
789
+ cb(mockService);
790
+ expect(updateService).toHaveBeenCalled();
791
+ });
792
+
793
+ it("updated callback publica notificación deployment-updated", async () => {
794
+ const { h } = await buildHandler();
795
+ const cb = (h.service.subscribe.updated as jest.Mock).mock.calls[0][0];
796
+ cb(mockService);
797
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
798
+ expect.objectContaining({ type: "success", subtype: "deployment-updated" }),
764
799
  );
765
- new BackendHandler(
766
- "deploy-service",
767
- dummyGlobalEventHandler,
768
- "httl://localhost:9080",
769
- "nightly"
800
+ });
801
+
802
+ it("delete callback publica notificación deployment-deleting y llama a deleteService", async () => {
803
+ const { deleteService } = await import("../api/service-api-service");
804
+ const { h } = await buildHandler();
805
+ const cb = (h.service.subscribe.delete as jest.Mock).mock.calls[0][0];
806
+ cb(mockService);
807
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
808
+ expect.objectContaining({ type: "info", subtype: "deployment-deleting" }),
770
809
  );
771
- new BackendHandler(
772
- "deploy-marketplace",
773
- dummyGlobalEventHandler,
774
- "httl://localhost:9080",
775
- "nightly"
810
+ expect(deleteService).toHaveBeenCalled();
811
+ });
812
+
813
+ it("deleted callback publica notificación deployment-deleted", async () => {
814
+ const { h } = await buildHandler();
815
+ const cb = (h.service.subscribe.deleted as jest.Mock).mock.calls[0][0];
816
+ cb(mockService);
817
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
818
+ expect.objectContaining({ type: "success", subtype: "deployment-deleted" }),
776
819
  );
777
- new BackendHandler(
778
- "marketplace",
779
- dummyGlobalEventHandler,
780
- "httl://localhost:9080",
781
- "nightly"
820
+ });
821
+
822
+ it("restart callback llama a restartService", async () => {
823
+ const { restartService } = await import("../api/service-api-service");
824
+ const { h } = await buildHandler();
825
+ const cb = (h.service.subscribe.restart as jest.Mock).mock.calls[0][0];
826
+ cb(mockService);
827
+ expect(restartService).toHaveBeenCalled();
828
+ });
829
+
830
+ it("requestRevisionData callback llama a requestRevisionData", async () => {
831
+ const { requestRevisionData } = await import("../api/service-api-service");
832
+ const { h } = await buildHandler();
833
+ const cb = (h.service.subscribe.requestRevisionData as jest.Mock).mock.calls[0][0];
834
+ cb(mockService);
835
+ expect(requestRevisionData).toHaveBeenCalled();
836
+ });
837
+
838
+ it("updateServiceLinks callback llama a updateServiceLinks", async () => {
839
+ const { updateServiceLinks } = await import("../api/service-api-service");
840
+ const { h } = await buildHandler();
841
+ const cb = (h.service.subscribe.updateServiceLinks as jest.Mock).mock.calls[0][0];
842
+ cb({ id: "link1" } as any);
843
+ expect(updateServiceLinks).toHaveBeenCalled();
844
+ });
845
+
846
+ it("changeRevision callback llama a changeRevision", async () => {
847
+ const { changeRevision } = await import("../api/service-api-service");
848
+ const { h } = await buildHandler();
849
+ const cb = (h.service.subscribe.changeRevision as jest.Mock).mock.calls[0][0];
850
+ cb(mockService);
851
+ expect(changeRevision).toHaveBeenCalled();
852
+ });
853
+
854
+ it("restartInstance callback llama a restartInstance", async () => {
855
+ const { restartInstance } = await import("../api/service-api-service");
856
+ const { h } = await buildHandler();
857
+ const cb = (h.service.subscribe.restartInstance as jest.Mock).mock.calls[0][0];
858
+ cb({ service: mockService, roleId: "r1", instanceId: "i1" });
859
+ expect(restartInstance).toHaveBeenCalledWith(mockService, "r1", "i1", "");
860
+ });
861
+
862
+ it("empty callbacks de service no lanzan error", async () => {
863
+ const { h } = await buildHandler();
864
+ const names = ["deletionError", "updateError", "requestLogs"];
865
+ for (const name of names) {
866
+ const cb = (h.service.subscribe[name as keyof typeof h.service.subscribe] as jest.Mock).mock.calls[0][0];
867
+ expect(() => cb(mockService)).not.toThrow();
868
+ }
869
+ });
870
+ });
871
+
872
+ // ── Callbacks de Marketplace ──────────────────────────────────────────────────
873
+
874
+ describe("BackendHandler - callbacks de Marketplace", () => {
875
+ it("deployItem callback llama a deployMarketplaceItem", async () => {
876
+ const { deployMarketplaceItem } = await import("../api/marketplace-api-service");
877
+ const { h } = await buildHandler();
878
+ const cb = (h.marketplace.subscribe.deployItem as jest.Mock).mock.calls[0][0];
879
+ cb({ deploymentData: { name: "item1" } } as MarketplaceService);
880
+ expect(deployMarketplaceItem).toHaveBeenCalled();
881
+ });
882
+
883
+ it("loadSchema callback exitoso publica schemaLoaded", async () => {
884
+ const { fetchAndStoreMarketplaceSchema } = await import("../websocket-manager");
885
+ (fetchAndStoreMarketplaceSchema as jest.Mock).mockResolvedValue(undefined);
886
+ const { h } = await buildHandler();
887
+ const cb = (h.marketplace.subscribe.loadSchema as jest.Mock).mock.calls[0][0];
888
+ const item: MarketplaceItem = { name: "item1" } as any;
889
+ await cb(JSON.stringify({ item }));
890
+ expect(fetchAndStoreMarketplaceSchema).toHaveBeenCalledWith(item);
891
+ expect(h.marketplace.publish.schemaLoaded).toHaveBeenCalledWith(item);
892
+ });
893
+
894
+ it("loadSchema callback con error publica schemaLoadError", async () => {
895
+ const { fetchAndStoreMarketplaceSchema } = await import("../websocket-manager");
896
+ (fetchAndStoreMarketplaceSchema as jest.Mock).mockRejectedValue(new Error("schema-err"));
897
+ const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
898
+ const { h } = await buildHandler();
899
+ const cb = (h.marketplace.subscribe.loadSchema as jest.Mock).mock.calls[0][0];
900
+ const payload = JSON.stringify({ item: { name: "item1" } });
901
+ await cb(payload);
902
+ expect(h.marketplace.publish.schemaLoadError).toHaveBeenCalledWith(payload);
903
+ consoleSpy.mockRestore();
904
+ });
905
+
906
+ it("loadSchema callback con JSON inválido publica schemaLoadError", async () => {
907
+ const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
908
+ const { h } = await buildHandler();
909
+ const cb = (h.marketplace.subscribe.loadSchema as jest.Mock).mock.calls[0][0];
910
+ await cb("not-valid-json");
911
+ expect(h.marketplace.publish.schemaLoadError).toHaveBeenCalled();
912
+ consoleSpy.mockRestore();
913
+ });
914
+
915
+ it("loadItems callback no lanza error", async () => {
916
+ const { h } = await buildHandler();
917
+ const cb = (h.marketplace.subscribe.loadItems as jest.Mock).mock.calls[0][0];
918
+ expect(() => cb(["t1", "t2"])).not.toThrow();
919
+ });
920
+
921
+ it("empty callbacks de marketplace no lanzan error", async () => {
922
+ const { h } = await buildHandler();
923
+ const names = ["itemDeployed", "deploymentError", "updateItem", "itemUpdated", "updateError", "deleteItem", "itemDeleted", "deletionError"];
924
+ for (const name of names) {
925
+ const cb = (h.marketplace.subscribe[name as keyof typeof h.marketplace.subscribe] as jest.Mock).mock.calls[0][0];
926
+ expect(() => cb({})).not.toThrow();
927
+ }
928
+ });
929
+ });
930
+
931
+ // ── Callbacks de Resource ─────────────────────────────────────────────────────
932
+
933
+ describe("BackendHandler - callbacks de Resource", () => {
934
+ const mockResource: Resource = { name: "res1", type: "domain", tenant: "ten1" } as any;
935
+
936
+ it("creation callback llama a createResource", async () => {
937
+ const { createResource } = await import("../api/resources-api-service");
938
+ const { h } = await buildHandler();
939
+ const cb = (h.resource.subscribe.creation as jest.Mock).mock.calls[0][0];
940
+ cb(mockResource);
941
+ expect(createResource).toHaveBeenCalledWith("ten1", mockResource, "");
942
+ });
943
+
944
+ it("creation callback con tenant undefined usa string vacío", async () => {
945
+ const { createResource } = await import("../api/resources-api-service");
946
+ const { h } = await buildHandler();
947
+ const cb = (h.resource.subscribe.creation as jest.Mock).mock.calls[0][0];
948
+ cb({ name: "r1", type: "domain" } as Resource);
949
+ expect(createResource).toHaveBeenCalledWith("", expect.anything(), "");
950
+ });
951
+
952
+ it("update callback llama a updateResource", async () => {
953
+ const { updateResource } = await import("../api/resources-api-service");
954
+ const { h } = await buildHandler();
955
+ const cb = (h.resource.subscribe.update as jest.Mock).mock.calls[0][0];
956
+ cb(mockResource);
957
+ expect(updateResource).toHaveBeenCalled();
958
+ });
959
+
960
+ it("delete callback llama a deleteResource", async () => {
961
+ const { deleteResource } = await import("../api/resources-api-service");
962
+ const { h } = await buildHandler();
963
+ const cb = (h.resource.subscribe.delete as jest.Mock).mock.calls[0][0];
964
+ cb(mockResource);
965
+ expect(deleteResource).toHaveBeenCalled();
966
+ });
967
+
968
+ it("deleted callback publica notificación resource-deleted", async () => {
969
+ const { h } = await buildHandler();
970
+ const cb = (h.resource.subscribe.deleted as jest.Mock).mock.calls[0][0];
971
+ cb({ name: "res1", type: "domain", tenant: "ten1" } as any);
972
+ expect(h.notification.publish.creation).toHaveBeenCalledWith(
973
+ expect.objectContaining({ type: "success", subtype: "resource-deleted", data: { resource: "res1", type: "domain", tenant: "ten1" } }),
782
974
  );
975
+ });
976
+
977
+ it("empty callbacks de resource no lanzan error", async () => {
978
+ const { h } = await buildHandler();
979
+ const names = ["created", "creationError", "updated", "updateError", "deletionError"];
980
+ for (const name of names) {
981
+ const cb = (h.resource.subscribe[name as keyof typeof h.resource.subscribe] as jest.Mock).mock.calls[0][0];
982
+ expect(() => cb({})).not.toThrow();
983
+ }
984
+ });
985
+ });
986
+
987
+ // ── Callbacks de Organization ─────────────────────────────────────────────────
988
+
989
+ describe("BackendHandler - callbacks de Organization", () => {
990
+ it("todos los callbacks de organization no lanzan error", async () => {
991
+ const { h } = await buildHandler();
992
+ const names = ["creation", "created", "creationError", "update", "updated", "updateError", "delete", "deleted", "deletionError"];
993
+ for (const name of names) {
994
+ const cb = (h.organization.subscribe[name as keyof typeof h.organization.subscribe] as jest.Mock).mock.calls[0][0];
995
+ expect(() => cb({})).not.toThrow();
996
+ }
997
+ });
998
+ });
999
+
1000
+ // ── Registry, invite y token empty callbacks ──────────────────────────────────
1001
+
1002
+ describe("BackendHandler - empty callbacks adicionales", () => {
1003
+ it("tenant registry empty callbacks no lanzan error", async () => {
1004
+ const { h } = await buildHandler();
1005
+ const names = ["registryCreated", "registryCreationError", "registryUpdated", "registryUpdateError", "registryDeleted", "registryDeletionError"];
1006
+ for (const name of names) {
1007
+ const cb = (h.tenant.subscribe[name as keyof typeof h.tenant.subscribe] as jest.Mock).mock.calls[0][0];
1008
+ expect(() => cb({})).not.toThrow();
1009
+ }
1010
+ });
1011
+
1012
+ it("tenant invite/user empty callbacks no lanzan error", async () => {
1013
+ const { h } = await buildHandler();
1014
+ const names = ["inviteError", "userRemoved", "removeUserError", "inviteUpdated", "inviteUpdateError", "inviteAccepted", "acceptInviteError", "inviteRejected", "inviteRejectError"];
1015
+ for (const name of names) {
1016
+ const cb = (h.tenant.subscribe[name as keyof typeof h.tenant.subscribe] as jest.Mock).mock.calls[0][0];
1017
+ expect(() => cb({})).not.toThrow();
1018
+ }
1019
+ });
1020
+
1021
+ it("tenant token non-subscribed mocks tienen cero llamadas", async () => {
1022
+ const { h } = await buildHandler();
1023
+ // tokenCreated/tokenCreationError/tokenDeleted/tokenDeletionError no son suscritos por BackendHandler
1024
+ expect((h.tenant.subscribe.tokenCreated as jest.Mock).mock.calls).toHaveLength(0);
1025
+ expect((h.tenant.subscribe.tokenDeleted as jest.Mock).mock.calls).toHaveLength(0);
1026
+ });
1027
+
1028
+ it("user empty callbacks no lanzan error", async () => {
1029
+ const { h } = await buildHandler();
1030
+ const names = ["created", "creationError", "updated", "updateError", "loadError", "deleted", "deletionError"];
1031
+ for (const name of names) {
1032
+ const cb = (h.user.subscribe[name as keyof typeof h.user.subscribe] as jest.Mock).mock.calls[0][0];
1033
+ expect(() => cb({})).not.toThrow();
1034
+ }
1035
+ });
1036
+
1037
+ it("account empty callbacks no lanzan error", async () => {
1038
+ const { h } = await buildHandler();
1039
+ const names = ["created", "creationError", "updated", "updateError", "deleted", "deletionError"];
1040
+ for (const name of names) {
1041
+ const cb = (h.account.subscribe[name as keyof typeof h.account.subscribe] as jest.Mock).mock.calls[0][0];
1042
+ expect(() => cb({})).not.toThrow();
1043
+ }
1044
+ });
783
1045
 
784
- expect(tenantCbSpy).toHaveBeenCalled();
785
- expect(userCbSpy).toHaveBeenCalled();
786
- expect(accountCbSpy).toHaveBeenCalled();
787
- expect(envCbSpy).toHaveBeenCalled();
788
- expect(serviceCbSpy).toHaveBeenCalled();
789
- expect(marketplaceDeploySpy).toHaveBeenCalled();
790
- expect(marketplaceLoadSpy).toHaveBeenCalled();
1046
+ it("environment empty callbacks no lanzan error", async () => {
1047
+ const { h } = await buildHandler();
1048
+ const names = ["creationError", "updated", "updateError", "deleted", "deletionError"];
1049
+ for (const name of names) {
1050
+ const cb = (h.environment.subscribe[name as keyof typeof h.environment.subscribe] as jest.Mock).mock.calls[0][0];
1051
+ expect(() => cb({})).not.toThrow();
1052
+ }
791
1053
  });
792
1054
  });