@taskforcehq/taskforce 0.3.302 → 0.3.303
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/Taskforce.module.css +11 -0
- package/dist/TaskforceCore.js +59 -9
- package/dist/TaskforceCore.test.js +717 -14
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +110 -51
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +106 -151
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/Taskforce.d.ts +51 -10
- package/dist/core/Taskforce.js +218 -49
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/types.d.ts +11 -12
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskforce.d.ts +3 -0
- package/dist/hooks/useTaskforce.js +9 -4
- package/dist/mcp/runtime.js +90 -29
- package/dist/mcp/runtime.test.js +272 -81
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.test.js +2 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +80 -23
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +194 -53
- package/dist/server/routes/billing.js +506 -23
- package/dist/server/routes/billing.test.js +937 -45
- package/dist/server/routes/shared.js +5 -3
- package/dist/server/routes/sync.integration.test.js +160 -0
- package/dist/server/routes/sync.js +15 -5
- package/dist/server/routes.js +3 -3
- package/dist/server/routes.test.js +287 -76
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -16
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/workspacePullFeed.d.ts +10 -0
- package/dist/sync/workspacePullFeed.js +111 -63
- package/dist/sync/workspacePullFeed.test.js +112 -0
- package/dist/ui/assets/{AgentsModule-JG5jc3he.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-C3GyzrWm.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/{TaskSettings-Bqd4cCzW.js → TaskSettings-BOC5F6Ag.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/package.json +3 -3
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -236,6 +236,7 @@ vi.mock('../core/Taskforce', () => {
|
|
|
236
236
|
setSystemRole = vi.fn().mockReturnValue(true);
|
|
237
237
|
setUserDisabledGlobal = vi.fn().mockReturnValue(true);
|
|
238
238
|
setUserBetaAccessGlobal = vi.fn().mockReturnValue(true);
|
|
239
|
+
purgeSystemUser = vi.fn();
|
|
239
240
|
updateWorkspaceUserRole = vi.fn();
|
|
240
241
|
inviteWorkspaceUser = vi.fn();
|
|
241
242
|
updateWorkspaceProfile = vi.fn().mockImplementation((workspaceId, input) => ({
|
|
@@ -262,17 +263,16 @@ vi.mock('../core/Taskforce', () => {
|
|
|
262
263
|
revokeInviteAcceptanceForUser = vi.fn().mockReturnValue({ revoked: 1 });
|
|
263
264
|
getWorkspacePlanMode = vi.fn().mockReturnValue('team');
|
|
264
265
|
setWorkspacePlanMode = vi.fn().mockImplementation((_workspaceId, mode) => mode);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
getOnboardingPolicy = vi.fn().mockReturnValue({
|
|
267
|
+
mode: 'require_plan_selection',
|
|
268
|
+
planVersionId: null
|
|
268
269
|
});
|
|
269
|
-
|
|
270
|
+
validateOnboardingPolicy = vi.fn().mockReturnValue({ valid: true });
|
|
270
271
|
getAccountAccessStatus = vi.fn().mockReturnValue({
|
|
271
272
|
gate: 'ok',
|
|
272
273
|
hasEntitlement: true,
|
|
273
274
|
canAccessApp: true,
|
|
274
275
|
canAccessPlans: true,
|
|
275
|
-
signupMonetizationStrategy: 'auto_assign_default',
|
|
276
276
|
planSelectionRequired: false,
|
|
277
277
|
message: null,
|
|
278
278
|
entitlement: {
|
|
@@ -3673,7 +3673,7 @@ describe('Server Routes', () => {
|
|
|
3673
3673
|
process.env.VITE_TASKFORCE_API_BASE_URL = previousApiBaseUrl;
|
|
3674
3674
|
}
|
|
3675
3675
|
});
|
|
3676
|
-
it('POST /api/taskforce/auth/register should create account
|
|
3676
|
+
it('POST /api/taskforce/auth/register should create account without a session cookie when verification is required', async () => {
|
|
3677
3677
|
const authConfig = {
|
|
3678
3678
|
runtimeMode: 'cloud',
|
|
3679
3679
|
enabled: true,
|
|
@@ -3706,6 +3706,7 @@ describe('Server Routes', () => {
|
|
|
3706
3706
|
email: 'owner@example.com',
|
|
3707
3707
|
role: 'owner'
|
|
3708
3708
|
}));
|
|
3709
|
+
expect(res.setHeader).not.toHaveBeenCalledWith('Set-Cookie', expect.any(String));
|
|
3709
3710
|
expect(res.writeHead).toHaveBeenCalledWith(201, expect.any(Object));
|
|
3710
3711
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"verificationRequired":true'));
|
|
3711
3712
|
});
|
|
@@ -3741,7 +3742,7 @@ describe('Server Routes', () => {
|
|
|
3741
3742
|
workspaceId: undefined,
|
|
3742
3743
|
role: 'member'
|
|
3743
3744
|
}));
|
|
3744
|
-
expect(res.setHeader).toHaveBeenCalledWith('Set-Cookie', expect.
|
|
3745
|
+
expect(res.setHeader).not.toHaveBeenCalledWith('Set-Cookie', expect.any(String));
|
|
3745
3746
|
expect(res.writeHead).toHaveBeenCalledWith(201, expect.any(Object));
|
|
3746
3747
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"workspaceSetupRequired":true'));
|
|
3747
3748
|
});
|
|
@@ -3780,6 +3781,7 @@ describe('Server Routes', () => {
|
|
|
3780
3781
|
const res = createMockRes();
|
|
3781
3782
|
await match?.handler(req, res, {});
|
|
3782
3783
|
expect(core.requestEmailVerification).not.toHaveBeenCalled();
|
|
3784
|
+
expect(res.setHeader).toHaveBeenCalledWith('Set-Cookie', expect.stringContaining('taskforce_session='));
|
|
3783
3785
|
expect(res.writeHead).toHaveBeenCalledWith(201, expect.any(Object));
|
|
3784
3786
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"verificationRequired":false'));
|
|
3785
3787
|
});
|
|
@@ -3854,7 +3856,7 @@ describe('Server Routes', () => {
|
|
|
3854
3856
|
code: 'EMAIL_ALREADY_EXISTS'
|
|
3855
3857
|
}));
|
|
3856
3858
|
});
|
|
3857
|
-
it('POST /api/taskforce/auth/register should fail when
|
|
3859
|
+
it('POST /api/taskforce/auth/register should fail when the onboarding policy is misconfigured', async () => {
|
|
3858
3860
|
const authConfig = {
|
|
3859
3861
|
runtimeMode: 'cloud',
|
|
3860
3862
|
enabled: true,
|
|
@@ -3867,7 +3869,7 @@ describe('Server Routes', () => {
|
|
|
3867
3869
|
sessionTtlSeconds: 3600
|
|
3868
3870
|
};
|
|
3869
3871
|
routes = createRoutes(core, { authConfig });
|
|
3870
|
-
const error = new Error('Configured
|
|
3872
|
+
const error = new Error('Configured onboarding plan version "missing-v9" was not found.');
|
|
3871
3873
|
error.statusCode = 500;
|
|
3872
3874
|
error.code = 'SIGNUP_PLAN_VERSION_NOT_FOUND';
|
|
3873
3875
|
core.registerPasswordUser.mockImplementationOnce(() => {
|
|
@@ -3883,7 +3885,7 @@ describe('Server Routes', () => {
|
|
|
3883
3885
|
expect(res.writeHead).toHaveBeenCalledWith(500, expect.any(Object));
|
|
3884
3886
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify({
|
|
3885
3887
|
success: false,
|
|
3886
|
-
error: 'Configured
|
|
3888
|
+
error: 'Configured onboarding plan version "missing-v9" was not found.',
|
|
3887
3889
|
code: 'SIGNUP_PLAN_VERSION_NOT_FOUND'
|
|
3888
3890
|
}));
|
|
3889
3891
|
});
|
|
@@ -3900,12 +3902,19 @@ describe('Server Routes', () => {
|
|
|
3900
3902
|
sessionTtlSeconds: 3600
|
|
3901
3903
|
};
|
|
3902
3904
|
routes = createRoutes(core, { authConfig });
|
|
3905
|
+
core.inspectOneTimeAuthToken = vi.fn().mockReturnValueOnce({
|
|
3906
|
+
state: 'valid',
|
|
3907
|
+
userId: 'u-verify',
|
|
3908
|
+
workspaceId: 'workspace-1'
|
|
3909
|
+
});
|
|
3903
3910
|
core.verifyEmailWithToken.mockReturnValueOnce(true);
|
|
3904
3911
|
const match = matchRoute('POST', '/api/taskforce/auth/verify-email/confirm', routes);
|
|
3905
3912
|
const req = createMockReq('POST', '/api/taskforce/auth/verify-email/confirm', { token: 'tok1' });
|
|
3906
3913
|
const res = createMockRes();
|
|
3907
3914
|
await match?.handler(req, res, {});
|
|
3915
|
+
expect(core.inspectOneTimeAuthToken).toHaveBeenCalledWith('tok1', 'email_verification');
|
|
3908
3916
|
expect(core.verifyEmailWithToken).toHaveBeenCalledWith('tok1');
|
|
3917
|
+
expect(res.setHeader).toHaveBeenCalledWith('Set-Cookie', expect.stringContaining('taskforce_session='));
|
|
3909
3918
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
3910
3919
|
});
|
|
3911
3920
|
it('POST /api/taskforce/auth/password-reset/confirm should set new password', async () => {
|
|
@@ -4779,10 +4788,10 @@ describe('Server Routes', () => {
|
|
|
4779
4788
|
}, { cookie });
|
|
4780
4789
|
const res = createMockRes();
|
|
4781
4790
|
await match?.handler(req, res, {});
|
|
4782
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
4783
|
-
tasks: [{ id: 'task-1', title: 'Task 1' }],
|
|
4784
|
-
archived: [{ id: 'task-2', title: 'Task 2', isArchived: true }]
|
|
4785
|
-
}, 'workspace-1');
|
|
4791
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
4792
|
+
tasks: [expect.objectContaining({ id: 'task-1', title: 'Task 1' })],
|
|
4793
|
+
archived: [expect.objectContaining({ id: 'task-2', title: 'Task 2', isArchived: true })]
|
|
4794
|
+
}), 'workspace-1');
|
|
4786
4795
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
4787
4796
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"success":true'));
|
|
4788
4797
|
expect(core.addAdminAuditLog).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -6427,10 +6436,10 @@ describe('Server Routes', () => {
|
|
|
6427
6436
|
const res = createMockRes();
|
|
6428
6437
|
await match?.handler(req, res, {});
|
|
6429
6438
|
expect(core.deleteTaskForSync).toHaveBeenCalledWith('task-2', 'workspace-local', { persistBackup: false });
|
|
6430
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
6431
|
-
tasks: [{ id: 'task-1', title: 'Task 1' }],
|
|
6439
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
6440
|
+
tasks: [expect.objectContaining({ id: 'task-1', title: 'Task 1' })],
|
|
6432
6441
|
archived: []
|
|
6433
|
-
}, 'workspace-local');
|
|
6442
|
+
}), 'workspace-local');
|
|
6434
6443
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
6435
6444
|
});
|
|
6436
6445
|
it('POST /api/taskforce/sync/workspace/apply-local should apply document review session upserts and deletes', async () => {
|
|
@@ -6868,10 +6877,10 @@ describe('Server Routes', () => {
|
|
|
6868
6877
|
});
|
|
6869
6878
|
const res = createMockRes();
|
|
6870
6879
|
await match?.handler(req, res, {});
|
|
6871
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
6872
|
-
tasks: [{ id: 'task-shared-1', title: 'Shared task' }],
|
|
6880
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
6881
|
+
tasks: [expect.objectContaining({ id: 'task-shared-1', title: 'Shared task' })],
|
|
6873
6882
|
archived: []
|
|
6874
|
-
}, 'workspace-shared-local');
|
|
6883
|
+
}), 'workspace-shared-local');
|
|
6875
6884
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
6876
6885
|
});
|
|
6877
6886
|
it('POST /api/taskforce/sync/workspace/apply-local should route archived upserts and preserve canceledReason', async () => {
|
|
@@ -6898,17 +6907,17 @@ describe('Server Routes', () => {
|
|
|
6898
6907
|
});
|
|
6899
6908
|
const res = createMockRes();
|
|
6900
6909
|
await match?.handler(req, res, {});
|
|
6901
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
6910
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
6902
6911
|
tasks: [],
|
|
6903
|
-
archived: [{
|
|
6912
|
+
archived: [expect.objectContaining({
|
|
6904
6913
|
id: 'task-archived-cancelled',
|
|
6905
6914
|
title: 'Cancelled + Archived',
|
|
6906
6915
|
status: 'cancelled',
|
|
6907
6916
|
canceledReason: 'Scope changed',
|
|
6908
6917
|
workstreamId: 'workstream-archived-1',
|
|
6909
6918
|
isArchived: true
|
|
6910
|
-
}]
|
|
6911
|
-
}, 'workspace-local');
|
|
6919
|
+
})]
|
|
6920
|
+
}), 'workspace-local');
|
|
6912
6921
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
6913
6922
|
});
|
|
6914
6923
|
it('POST /api/taskforce/sync/workspace/apply-local should prioritize delete over same-task upsert', async () => {
|
|
@@ -7575,10 +7584,10 @@ describe('Server Routes', () => {
|
|
|
7575
7584
|
}, { cookie });
|
|
7576
7585
|
const res1 = createMockRes();
|
|
7577
7586
|
await match?.handler(req1, res1, {});
|
|
7578
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
7579
|
-
tasks: [{ id: 'task-1', title: 'Task 1' }],
|
|
7587
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
7588
|
+
tasks: [expect.objectContaining({ id: 'task-1', title: 'Task 1' })],
|
|
7580
7589
|
archived: []
|
|
7581
|
-
}, 'workspace-1');
|
|
7590
|
+
}), 'workspace-1');
|
|
7582
7591
|
expect(res1.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
7583
7592
|
const firstPayload = JSON.parse(String(res1.end.mock.calls.at(-1)?.[0] || '{}'));
|
|
7584
7593
|
expect(Array.isArray(firstPayload.emittedEventIds)).toBe(true);
|
|
@@ -7780,10 +7789,10 @@ describe('Server Routes', () => {
|
|
|
7780
7789
|
}, { cookie });
|
|
7781
7790
|
const res = createMockRes();
|
|
7782
7791
|
await match?.handler(req, res, {});
|
|
7783
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
7784
|
-
tasks: [{ id: 'task-1', title: 'Task 1' }],
|
|
7792
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
7793
|
+
tasks: [expect.objectContaining({ id: 'task-1', title: 'Task 1' })],
|
|
7785
7794
|
archived: []
|
|
7786
|
-
}, 'workspace-local-active');
|
|
7795
|
+
}), 'workspace-local-active');
|
|
7787
7796
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
7788
7797
|
expect(core.listUserWorkspaces).toHaveBeenCalledWith('user-1');
|
|
7789
7798
|
});
|
|
@@ -8060,15 +8069,15 @@ describe('Server Routes', () => {
|
|
|
8060
8069
|
}, { cookie });
|
|
8061
8070
|
const res = createMockRes();
|
|
8062
8071
|
await match?.handler(req, res, {});
|
|
8063
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
8064
|
-
tasks: [{
|
|
8072
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
8073
|
+
tasks: [expect.objectContaining({
|
|
8065
8074
|
id: 'task-cancelled-1',
|
|
8066
8075
|
title: 'Cancelled Task',
|
|
8067
8076
|
status: 'cancelled',
|
|
8068
8077
|
canceledReason: 'No longer needed'
|
|
8069
|
-
}],
|
|
8078
|
+
})],
|
|
8070
8079
|
archived: []
|
|
8071
|
-
}, 'workspace-1');
|
|
8080
|
+
}), 'workspace-1');
|
|
8072
8081
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
8073
8082
|
});
|
|
8074
8083
|
it('POST /api/taskforce/sync/workspace/push should preserve workstreamId in upsert payload', async () => {
|
|
@@ -8109,15 +8118,15 @@ describe('Server Routes', () => {
|
|
|
8109
8118
|
}, { cookie });
|
|
8110
8119
|
const res = createMockRes();
|
|
8111
8120
|
await match?.handler(req, res, {});
|
|
8112
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
8113
|
-
tasks: [{
|
|
8121
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
8122
|
+
tasks: [expect.objectContaining({
|
|
8114
8123
|
id: 'task-workstream-1',
|
|
8115
8124
|
title: 'Linked task',
|
|
8116
8125
|
status: 'task',
|
|
8117
8126
|
workstreamId: 'workstream-1'
|
|
8118
|
-
}],
|
|
8127
|
+
})],
|
|
8119
8128
|
archived: []
|
|
8120
|
-
}, 'workspace-1');
|
|
8129
|
+
}), 'workspace-1');
|
|
8121
8130
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
8122
8131
|
});
|
|
8123
8132
|
it('POST /api/taskforce/sync/workspace/push should route archived upserts into archived snapshot list', async () => {
|
|
@@ -8158,15 +8167,15 @@ describe('Server Routes', () => {
|
|
|
8158
8167
|
}, { cookie });
|
|
8159
8168
|
const res = createMockRes();
|
|
8160
8169
|
await match?.handler(req, res, {});
|
|
8161
|
-
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
8170
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
8162
8171
|
tasks: [],
|
|
8163
|
-
archived: [{
|
|
8172
|
+
archived: [expect.objectContaining({
|
|
8164
8173
|
id: 'task-archived-1',
|
|
8165
8174
|
title: 'Archived Task',
|
|
8166
8175
|
workstreamId: 'workstream-archived-1',
|
|
8167
8176
|
isArchived: true
|
|
8168
|
-
}]
|
|
8169
|
-
}, 'workspace-1');
|
|
8177
|
+
})]
|
|
8178
|
+
}), 'workspace-1');
|
|
8170
8179
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
8171
8180
|
});
|
|
8172
8181
|
it('GET /api/taskforce/sync/workspace/pull should include canceledReason and archived marker in response payload', async () => {
|
|
@@ -8292,7 +8301,7 @@ describe('Server Routes', () => {
|
|
|
8292
8301
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
8293
8302
|
const response = JSON.parse(String(res.end.mock.calls[0]?.[0] || '{}'));
|
|
8294
8303
|
const changes = Array.isArray(response?.changes) ? response.changes : [];
|
|
8295
|
-
expect(changes).toContainEqual({
|
|
8304
|
+
expect(changes).toContainEqual(expect.objectContaining({
|
|
8296
8305
|
op: 'taxonomy-upsert',
|
|
8297
8306
|
taxonomyState: {
|
|
8298
8307
|
categories: [{ value: 'default', label: 'General' }],
|
|
@@ -8328,8 +8337,9 @@ describe('Server Routes', () => {
|
|
|
8328
8337
|
filterEnabled: true,
|
|
8329
8338
|
sortEnabled: true
|
|
8330
8339
|
}],
|
|
8340
|
+
updatedAt: '2026-02-22T10:00:00.000Z',
|
|
8331
8341
|
watermark: '2026-02-22T10:00:00.000Z'
|
|
8332
|
-
});
|
|
8342
|
+
}));
|
|
8333
8343
|
});
|
|
8334
8344
|
it('POST /api/taskforce/workspaces should create workspace for authenticated user', async () => {
|
|
8335
8345
|
const authConfig = {
|
|
@@ -9014,13 +9024,68 @@ describe('Server Routes', () => {
|
|
|
9014
9024
|
allowed: true
|
|
9015
9025
|
});
|
|
9016
9026
|
});
|
|
9027
|
+
it('GET /api/taskforce/account/team-management-access should still resolve access when billing tables are unavailable', async () => {
|
|
9028
|
+
core.resolveUserAccess.mockReturnValueOnce({
|
|
9029
|
+
userId: 'u1',
|
|
9030
|
+
workspaceId: 'workspace-1',
|
|
9031
|
+
role: 'owner',
|
|
9032
|
+
disabled: false,
|
|
9033
|
+
betaAccess: true
|
|
9034
|
+
});
|
|
9035
|
+
core.getAccountAccessStatus.mockReturnValueOnce({
|
|
9036
|
+
gate: 'ok',
|
|
9037
|
+
hasEntitlement: true,
|
|
9038
|
+
canAccessApp: true,
|
|
9039
|
+
canAccessPlans: true,
|
|
9040
|
+
planSelectionRequired: false,
|
|
9041
|
+
message: null,
|
|
9042
|
+
entitlement: {
|
|
9043
|
+
state: 'active',
|
|
9044
|
+
planId: 'collab',
|
|
9045
|
+
planVersionId: 'collab-v1'
|
|
9046
|
+
}
|
|
9047
|
+
});
|
|
9048
|
+
core.getAccountEntitlementsPayload.mockReturnValueOnce({
|
|
9049
|
+
state: 'active',
|
|
9050
|
+
planId: 'collab',
|
|
9051
|
+
features: {
|
|
9052
|
+
'collaboration.team_management': { allowed: true }
|
|
9053
|
+
}
|
|
9054
|
+
});
|
|
9055
|
+
const baseDb = core.getDatabaseAdapter();
|
|
9056
|
+
core.getDatabaseAdapter = vi.fn().mockReturnValue({
|
|
9057
|
+
...baseDb,
|
|
9058
|
+
prepare: vi.fn((sql) => {
|
|
9059
|
+
if (sql.includes('FROM user_billing') || sql.includes('FROM plan_catalog')) {
|
|
9060
|
+
throw new Error('no such table');
|
|
9061
|
+
}
|
|
9062
|
+
return baseDb.prepare(sql);
|
|
9063
|
+
})
|
|
9064
|
+
});
|
|
9065
|
+
const match = matchRoute('GET', '/api/taskforce/account/team-management-access', routes);
|
|
9066
|
+
const req = createMockReq('GET', '/api/taskforce/account/team-management-access?workspaceId=workspace-1', undefined, {
|
|
9067
|
+
'x-taskforce-user-id': 'u1'
|
|
9068
|
+
});
|
|
9069
|
+
const res = createMockRes();
|
|
9070
|
+
await match?.handler(req, res, {});
|
|
9071
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
9072
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
9073
|
+
expect(payload).toMatchObject({
|
|
9074
|
+
workspaceId: 'workspace-1',
|
|
9075
|
+
role: 'owner',
|
|
9076
|
+
canManageWorkspace: true,
|
|
9077
|
+
teamPlanMode: 'team',
|
|
9078
|
+
allowed: true,
|
|
9079
|
+
gate: 'ok',
|
|
9080
|
+
planSelectionRequired: false
|
|
9081
|
+
});
|
|
9082
|
+
});
|
|
9017
9083
|
it('GET /api/taskforce/account/access-status should return pending plan-selection gate', async () => {
|
|
9018
9084
|
core.getAccountAccessStatus.mockReturnValueOnce({
|
|
9019
9085
|
gate: 'plan_selection_required',
|
|
9020
9086
|
hasEntitlement: false,
|
|
9021
9087
|
canAccessApp: false,
|
|
9022
9088
|
canAccessPlans: true,
|
|
9023
|
-
signupMonetizationStrategy: 'plan_selection_required',
|
|
9024
9089
|
planSelectionRequired: true,
|
|
9025
9090
|
message: 'Choose a plan to continue.',
|
|
9026
9091
|
entitlement: null
|
|
@@ -9046,7 +9111,6 @@ describe('Server Routes', () => {
|
|
|
9046
9111
|
hasEntitlement: false,
|
|
9047
9112
|
canAccessApp: false,
|
|
9048
9113
|
canAccessPlans: true,
|
|
9049
|
-
signupMonetizationStrategy: 'plan_selection_required',
|
|
9050
9114
|
planSelectionRequired: true,
|
|
9051
9115
|
message: 'Choose a plan to continue.',
|
|
9052
9116
|
entitlement: null
|
|
@@ -9061,6 +9125,87 @@ describe('Server Routes', () => {
|
|
|
9061
9125
|
expect(res.writeHead).toHaveBeenCalledWith(403, expect.any(Object));
|
|
9062
9126
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"code":"PLAN_SELECTION_REQUIRED"'));
|
|
9063
9127
|
});
|
|
9128
|
+
it('GET /api/taskforce/account/profile-summary should return checkout-pending recovery context', async () => {
|
|
9129
|
+
core.resolveUserAccess.mockReturnValueOnce({
|
|
9130
|
+
userId: 'u1',
|
|
9131
|
+
workspaceId: 'workspace-1',
|
|
9132
|
+
role: 'owner',
|
|
9133
|
+
disabled: false,
|
|
9134
|
+
betaAccess: true
|
|
9135
|
+
});
|
|
9136
|
+
core.getAccountAccessStatus.mockReturnValueOnce({
|
|
9137
|
+
gate: 'checkout_pending',
|
|
9138
|
+
hasEntitlement: true,
|
|
9139
|
+
canAccessApp: false,
|
|
9140
|
+
canAccessPlans: true,
|
|
9141
|
+
planSelectionRequired: false,
|
|
9142
|
+
message: 'Complete checkout to continue.',
|
|
9143
|
+
entitlement: {
|
|
9144
|
+
accountId: 'u1',
|
|
9145
|
+
planId: 'pro',
|
|
9146
|
+
planVersionId: 'pro-v1',
|
|
9147
|
+
state: 'checkout_pending'
|
|
9148
|
+
}
|
|
9149
|
+
});
|
|
9150
|
+
core.getAccountEntitlementsPayload.mockImplementationOnce(() => {
|
|
9151
|
+
const error = new Error('Entitlement not found');
|
|
9152
|
+
error.code = 'ENTITLEMENT_NOT_FOUND';
|
|
9153
|
+
throw error;
|
|
9154
|
+
});
|
|
9155
|
+
const match = matchRoute('GET', '/api/taskforce/account/profile-summary', routes);
|
|
9156
|
+
const req = createMockReq('GET', '/api/taskforce/account/profile-summary?workspaceId=workspace-1', undefined, {
|
|
9157
|
+
'x-taskforce-user-id': 'u1'
|
|
9158
|
+
});
|
|
9159
|
+
const res = createMockRes();
|
|
9160
|
+
await match?.handler(req, res, {});
|
|
9161
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
9162
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
9163
|
+
expect(payload).toMatchObject({
|
|
9164
|
+
planId: 'pro',
|
|
9165
|
+
planVersionId: 'pro-v1',
|
|
9166
|
+
entitlementState: 'checkout_pending',
|
|
9167
|
+
gate: 'checkout_pending',
|
|
9168
|
+
message: 'Complete checkout to continue.',
|
|
9169
|
+
billingConflictCode: null,
|
|
9170
|
+
planSelectionRequired: false,
|
|
9171
|
+
workspaceId: 'workspace-1',
|
|
9172
|
+
workspaceRole: 'owner',
|
|
9173
|
+
canManageWorkspace: true,
|
|
9174
|
+
teamPlanMode: 'personal',
|
|
9175
|
+
teamManagementAllowed: false
|
|
9176
|
+
});
|
|
9177
|
+
});
|
|
9178
|
+
it('GET /api/taskforce/account/entitlements should surface checkout-pending gate when billing is not complete', async () => {
|
|
9179
|
+
core.getAccountEntitlementsPayload.mockImplementationOnce(() => {
|
|
9180
|
+
const error = new Error('Entitlement not found');
|
|
9181
|
+
error.code = 'ENTITLEMENT_NOT_FOUND';
|
|
9182
|
+
throw error;
|
|
9183
|
+
});
|
|
9184
|
+
core.getAccountAccessStatus.mockReturnValueOnce({
|
|
9185
|
+
gate: 'checkout_pending',
|
|
9186
|
+
hasEntitlement: true,
|
|
9187
|
+
canAccessApp: false,
|
|
9188
|
+
canAccessPlans: true,
|
|
9189
|
+
planSelectionRequired: false,
|
|
9190
|
+
message: 'Complete checkout to continue.',
|
|
9191
|
+
entitlement: {
|
|
9192
|
+
accountId: 'u1',
|
|
9193
|
+
planId: 'pro',
|
|
9194
|
+
planVersionId: 'pro-v1',
|
|
9195
|
+
state: 'checkout_pending'
|
|
9196
|
+
}
|
|
9197
|
+
});
|
|
9198
|
+
const match = matchRoute('GET', '/api/taskforce/account/entitlements', routes);
|
|
9199
|
+
const req = createMockReq('GET', '/api/taskforce/account/entitlements', undefined, {
|
|
9200
|
+
'x-taskforce-user-id': 'u1',
|
|
9201
|
+
'x-taskforce-workspace-id': 'workspace-1'
|
|
9202
|
+
});
|
|
9203
|
+
const res = createMockRes();
|
|
9204
|
+
await match?.handler(req, res, {});
|
|
9205
|
+
expect(res.writeHead).toHaveBeenCalledWith(403, expect.any(Object));
|
|
9206
|
+
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"code":"CHECKOUT_PENDING"'));
|
|
9207
|
+
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"gate":"checkout_pending"'));
|
|
9208
|
+
});
|
|
9064
9209
|
it('GET /api/taskforce/config should block authenticated users who must select a plan first', async () => {
|
|
9065
9210
|
const authConfig = {
|
|
9066
9211
|
runtimeMode: 'cloud',
|
|
@@ -9084,7 +9229,6 @@ describe('Server Routes', () => {
|
|
|
9084
9229
|
hasEntitlement: false,
|
|
9085
9230
|
canAccessApp: false,
|
|
9086
9231
|
canAccessPlans: true,
|
|
9087
|
-
signupMonetizationStrategy: 'plan_selection_required',
|
|
9088
9232
|
planSelectionRequired: true,
|
|
9089
9233
|
message: 'Choose a plan to continue.',
|
|
9090
9234
|
entitlement: null
|
|
@@ -9123,7 +9267,6 @@ describe('Server Routes', () => {
|
|
|
9123
9267
|
hasEntitlement: false,
|
|
9124
9268
|
canAccessApp: false,
|
|
9125
9269
|
canAccessPlans: true,
|
|
9126
|
-
signupMonetizationStrategy: 'auto_assign_default',
|
|
9127
9270
|
planSelectionRequired: false,
|
|
9128
9271
|
message: 'No entitlement is linked to this account.',
|
|
9129
9272
|
entitlement: null
|
|
@@ -9716,6 +9859,64 @@ describe('Server Routes', () => {
|
|
|
9716
9859
|
}));
|
|
9717
9860
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
9718
9861
|
});
|
|
9862
|
+
it('DELETE /api/taskforce/admin/system-users/:id should purge a user in allowed cloud environments', async () => {
|
|
9863
|
+
const authConfig = { runtimeMode: 'cloud', enabled: false };
|
|
9864
|
+
routes = createRoutes(core, { authConfig });
|
|
9865
|
+
core.purgeSystemUser.mockReturnValue({
|
|
9866
|
+
targetUserId: 'u1',
|
|
9867
|
+
email: 'u1@example.com',
|
|
9868
|
+
unassignedTaskCount: 2,
|
|
9869
|
+
clearedInitiativeOwnerCount: 0,
|
|
9870
|
+
clearedWorkstreamOwnerCount: 0,
|
|
9871
|
+
deletedWorkspaceMembershipCount: 1,
|
|
9872
|
+
deletedAuthTokenCount: 1,
|
|
9873
|
+
deletedAvatarDraftCount: 0,
|
|
9874
|
+
deletedMcpAccessTokenCount: 1,
|
|
9875
|
+
deletedMcpOauthAuthCodeCount: 0,
|
|
9876
|
+
deletedMcpOauthConnectorGrantCount: 0,
|
|
9877
|
+
deletedUiStateCount: 1,
|
|
9878
|
+
deletedUserPreferenceCount: 1,
|
|
9879
|
+
deletedEntitlementCount: 1,
|
|
9880
|
+
deletedUserBillingCount: 1,
|
|
9881
|
+
deletedUserCount: 1
|
|
9882
|
+
});
|
|
9883
|
+
const match = matchRoute('DELETE', '/api/taskforce/admin/system-users/u1', routes);
|
|
9884
|
+
const req = createMockReq('DELETE', '/api/taskforce/admin/system-users/u1', { emailConfirmation: 'u1@example.com' }, {
|
|
9885
|
+
'x-taskforce-system-role': 'system_admin',
|
|
9886
|
+
'x-taskforce-user-id': 'admin-user',
|
|
9887
|
+
host: 'performance-app.taskforcehq.ai',
|
|
9888
|
+
'x-forwarded-proto': 'https'
|
|
9889
|
+
});
|
|
9890
|
+
const res = createMockRes();
|
|
9891
|
+
await match?.handler(req, res, { id: 'u1' });
|
|
9892
|
+
expect(core.purgeSystemUser).toHaveBeenCalledWith({
|
|
9893
|
+
targetUserId: 'u1',
|
|
9894
|
+
actorUserId: 'admin-user',
|
|
9895
|
+
emailConfirmation: 'u1@example.com'
|
|
9896
|
+
});
|
|
9897
|
+
expect(core.addAdminAuditLog).toHaveBeenCalledWith(expect.objectContaining({
|
|
9898
|
+
action: 'purge-system-user',
|
|
9899
|
+
actorRole: 'system_admin',
|
|
9900
|
+
workspaceId: 'system'
|
|
9901
|
+
}));
|
|
9902
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
9903
|
+
});
|
|
9904
|
+
it('DELETE /api/taskforce/admin/system-users/:id should reject production environment purges', async () => {
|
|
9905
|
+
const authConfig = { runtimeMode: 'cloud', enabled: false };
|
|
9906
|
+
routes = createRoutes(core, { authConfig });
|
|
9907
|
+
const match = matchRoute('DELETE', '/api/taskforce/admin/system-users/u1', routes);
|
|
9908
|
+
const req = createMockReq('DELETE', '/api/taskforce/admin/system-users/u1', { emailConfirmation: 'u1@example.com' }, {
|
|
9909
|
+
'x-taskforce-system-role': 'system_admin',
|
|
9910
|
+
'x-taskforce-user-id': 'admin-user',
|
|
9911
|
+
host: 'app.taskforcehq.ai',
|
|
9912
|
+
'x-forwarded-proto': 'https'
|
|
9913
|
+
});
|
|
9914
|
+
const res = createMockRes();
|
|
9915
|
+
await match?.handler(req, res, { id: 'u1' });
|
|
9916
|
+
expect(core.purgeSystemUser).not.toHaveBeenCalled();
|
|
9917
|
+
expect(res.writeHead).toHaveBeenCalledWith(403, expect.any(Object));
|
|
9918
|
+
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"code":"SYSTEM_USER_PURGE_ENVIRONMENT_FORBIDDEN"'));
|
|
9919
|
+
});
|
|
9719
9920
|
it('removed workspace-based plan route should not match', () => {
|
|
9720
9921
|
expect(matchRoute('PATCH', '/api/taskforce/admin/workspace/plan', routes)).toBeNull();
|
|
9721
9922
|
expect(matchRoute('GET', '/api/taskforce/workspace/assignee-options', routes)).not.toBeNull();
|
|
@@ -10033,7 +10234,7 @@ describe('Server Routes', () => {
|
|
|
10033
10234
|
await match?.handler(req, res, {});
|
|
10034
10235
|
expect(res.writeHead).toHaveBeenCalledWith(403, expect.any(Object));
|
|
10035
10236
|
});
|
|
10036
|
-
it('GET /api/taskforce/admin/system-settings should include
|
|
10237
|
+
it('GET /api/taskforce/admin/system-settings should include onboarding policy fields', async () => {
|
|
10037
10238
|
core.getGlobalSettings.mockReturnValue({
|
|
10038
10239
|
auth: {
|
|
10039
10240
|
allowSelfRegistration: true,
|
|
@@ -10041,26 +10242,30 @@ describe('Server Routes', () => {
|
|
|
10041
10242
|
defaultNewUserBetaAccess: false,
|
|
10042
10243
|
authRateLimitEnabled: true,
|
|
10043
10244
|
authRateLimitMaxRequests: 10,
|
|
10044
|
-
authRateLimitWindowMs: 900000
|
|
10045
|
-
|
|
10046
|
-
|
|
10245
|
+
authRateLimitWindowMs: 900000
|
|
10246
|
+
},
|
|
10247
|
+
billing: {
|
|
10248
|
+
onboardingPolicy: {
|
|
10249
|
+
mode: 'auto_grant_free',
|
|
10250
|
+
planVersionId: 'starter-v1'
|
|
10251
|
+
}
|
|
10047
10252
|
}
|
|
10048
10253
|
});
|
|
10049
|
-
core.
|
|
10050
|
-
|
|
10051
|
-
|
|
10254
|
+
core.getOnboardingPolicy.mockReturnValue({
|
|
10255
|
+
mode: 'auto_grant_free',
|
|
10256
|
+
planVersionId: 'starter-v1'
|
|
10052
10257
|
});
|
|
10053
10258
|
const match = matchRoute('GET', '/api/taskforce/admin/system-settings', routes);
|
|
10054
10259
|
const req = createMockReq('GET', '/api/taskforce/admin/system-settings', undefined, { 'x-taskforce-role': 'admin' });
|
|
10055
10260
|
const res = createMockRes();
|
|
10056
10261
|
await match?.handler(req, res, {});
|
|
10057
10262
|
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
10058
|
-
expect(payload.status.
|
|
10059
|
-
expect(payload.status.
|
|
10060
|
-
expect(payload.settings.
|
|
10061
|
-
expect(payload.settings.
|
|
10263
|
+
expect(payload.status.onboardingPolicyMode).toBe('auto_grant_free');
|
|
10264
|
+
expect(payload.status.onboardingPlanVersionId).toBe('starter-v1');
|
|
10265
|
+
expect(payload.settings.onboardingPolicyMode).toBe('auto_grant_free');
|
|
10266
|
+
expect(payload.settings.onboardingPlanVersionId).toBe('starter-v1');
|
|
10062
10267
|
});
|
|
10063
|
-
it('POST /api/taskforce/admin/system-settings should persist
|
|
10268
|
+
it('POST /api/taskforce/admin/system-settings should persist onboarding policy fields', async () => {
|
|
10064
10269
|
const match = matchRoute('POST', '/api/taskforce/admin/system-settings', routes);
|
|
10065
10270
|
const req = createMockReq('POST', '/api/taskforce/admin/system-settings', {
|
|
10066
10271
|
allowSelfRegistration: true,
|
|
@@ -10069,14 +10274,14 @@ describe('Server Routes', () => {
|
|
|
10069
10274
|
authRateLimitEnabled: true,
|
|
10070
10275
|
authRateLimitMaxRequests: 10,
|
|
10071
10276
|
authRateLimitWindowMs: 900000,
|
|
10072
|
-
|
|
10073
|
-
|
|
10277
|
+
onboardingPolicyMode: 'auto_grant_free',
|
|
10278
|
+
onboardingPlanVersionId: 'starter-v1'
|
|
10074
10279
|
}, { 'x-taskforce-role': 'admin' });
|
|
10075
10280
|
const res = createMockRes();
|
|
10076
10281
|
await match?.handler(req, res, {});
|
|
10077
|
-
expect(core.
|
|
10078
|
-
|
|
10079
|
-
|
|
10282
|
+
expect(core.validateOnboardingPolicy).toHaveBeenCalledWith({
|
|
10283
|
+
mode: 'auto_grant_free',
|
|
10284
|
+
planVersionId: 'starter-v1'
|
|
10080
10285
|
});
|
|
10081
10286
|
expect(core.updateGlobalSettings).toHaveBeenCalledWith({
|
|
10082
10287
|
auth: {
|
|
@@ -10085,9 +10290,13 @@ describe('Server Routes', () => {
|
|
|
10085
10290
|
defaultNewUserBetaAccess: false,
|
|
10086
10291
|
authRateLimitEnabled: true,
|
|
10087
10292
|
authRateLimitMaxRequests: 10,
|
|
10088
|
-
authRateLimitWindowMs: 900000
|
|
10089
|
-
|
|
10090
|
-
|
|
10293
|
+
authRateLimitWindowMs: 900000
|
|
10294
|
+
},
|
|
10295
|
+
billing: {
|
|
10296
|
+
onboardingPolicy: {
|
|
10297
|
+
mode: 'auto_grant_free',
|
|
10298
|
+
planVersionId: 'starter-v1'
|
|
10299
|
+
}
|
|
10091
10300
|
}
|
|
10092
10301
|
});
|
|
10093
10302
|
});
|
|
@@ -10196,23 +10405,25 @@ describe('Server Routes', () => {
|
|
|
10196
10405
|
expect(res.end.mock.calls[0][0]).toContain('window.__TASKFORCE_SITE_CONFIG__ = ');
|
|
10197
10406
|
expect(res.end.mock.calls[0][0]).toContain('"pricingPageEnabled":false');
|
|
10198
10407
|
});
|
|
10199
|
-
it('POST /api/taskforce/admin/system-settings should reject invalid
|
|
10200
|
-
core.
|
|
10408
|
+
it('POST /api/taskforce/admin/system-settings should reject invalid onboarding settings', async () => {
|
|
10409
|
+
core.validateOnboardingPolicy.mockReturnValueOnce({
|
|
10201
10410
|
valid: false,
|
|
10202
|
-
code: '
|
|
10203
|
-
message: '
|
|
10411
|
+
code: 'ONBOARDING_PLAN_VERSION_REQUIRED',
|
|
10412
|
+
message: 'An onboarding plan version is required for the selected onboarding mode.'
|
|
10204
10413
|
});
|
|
10205
10414
|
const match = matchRoute('POST', '/api/taskforce/admin/system-settings', routes);
|
|
10206
10415
|
const req = createMockReq('POST', '/api/taskforce/admin/system-settings', {
|
|
10207
|
-
|
|
10208
|
-
|
|
10416
|
+
onboardingPolicyMode: 'auto_grant_free',
|
|
10417
|
+
onboardingPlanVersionId: null
|
|
10209
10418
|
}, { 'x-taskforce-role': 'admin' });
|
|
10210
10419
|
const res = createMockRes();
|
|
10211
10420
|
await match?.handler(req, res, {});
|
|
10212
10421
|
expect(res.writeHead).toHaveBeenCalledWith(400, expect.any(Object));
|
|
10213
10422
|
expect(core.updateGlobalSettings).not.toHaveBeenCalledWith(expect.objectContaining({
|
|
10214
|
-
|
|
10215
|
-
|
|
10423
|
+
billing: expect.objectContaining({
|
|
10424
|
+
onboardingPolicy: expect.objectContaining({
|
|
10425
|
+
mode: 'auto_grant_free'
|
|
10426
|
+
})
|
|
10216
10427
|
})
|
|
10217
10428
|
}));
|
|
10218
10429
|
});
|
|
@@ -25,6 +25,7 @@ export declare function pullWorkspaceChanges(resolveCloudAuthUrl: (path: string)
|
|
|
25
25
|
nextCursor?: string | null;
|
|
26
26
|
hasMore?: boolean;
|
|
27
27
|
workspaceMembers?: WorkspaceSyncWorkspaceMemberSnapshot[];
|
|
28
|
+
diagnostics?: Record<string, unknown>;
|
|
28
29
|
}>>;
|
|
29
30
|
export declare function pushWorkspaceChanges(resolveCloudAuthUrl: (path: string) => string, input: {
|
|
30
31
|
workspaceId: string;
|