@taskforcehq/taskforce 0.3.302 → 0.3.304
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 +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- 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/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- 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/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- 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
|
@@ -4,39 +4,77 @@ import os from 'os';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { TaskforceCore } from './Taskforce';
|
|
6
6
|
function makeProjectRoot() {
|
|
7
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-
|
|
7
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-onboarding-policy-'));
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
const core = new TaskforceCore({
|
|
11
|
-
projectRoot,
|
|
12
|
-
storagePath: path.join(projectRoot, '.taskforce'),
|
|
13
|
-
tenantId: path.basename(projectRoot)
|
|
14
|
-
});
|
|
9
|
+
function insertFreePriceMapping(core, planVersionId) {
|
|
15
10
|
const db = core.db;
|
|
11
|
+
const tenantId = core.getTenantId();
|
|
16
12
|
const now = new Date().toISOString();
|
|
17
|
-
core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter' });
|
|
18
13
|
db.prepare(`
|
|
19
14
|
INSERT OR IGNORE INTO billing_price_mappings (
|
|
20
15
|
tenant_id, plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
|
|
21
16
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
22
|
-
`).run(
|
|
17
|
+
`).run(tenantId, planVersionId, 'month', `free:${planVersionId}:month`, 'free', 1, 0, null, now, now);
|
|
18
|
+
}
|
|
19
|
+
function openCore(projectRoot) {
|
|
20
|
+
const core = new TaskforceCore({
|
|
21
|
+
projectRoot,
|
|
22
|
+
storagePath: path.join(projectRoot, '.taskforce'),
|
|
23
|
+
tenantId: path.basename(projectRoot)
|
|
24
|
+
});
|
|
23
25
|
core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'active' });
|
|
26
|
+
core.updatePlanVersion({
|
|
27
|
+
planId: 'starter',
|
|
28
|
+
versionNumber: 1,
|
|
29
|
+
seatLimit: 1
|
|
30
|
+
});
|
|
24
31
|
core.updatePlanVersionFeatures({
|
|
25
32
|
planVersionId: 'starter-v1',
|
|
26
33
|
features: [
|
|
27
34
|
{ featureKey: 'workspace.switching', access: 'enabled' }
|
|
28
35
|
]
|
|
29
36
|
});
|
|
37
|
+
insertFreePriceMapping(core, 'starter-v1');
|
|
30
38
|
return core;
|
|
31
39
|
}
|
|
32
|
-
describe('TaskforceCore
|
|
33
|
-
it('
|
|
40
|
+
describe('TaskforceCore onboarding policy', () => {
|
|
41
|
+
it('defaults to auto_grant_free when a free personal plan version is available', () => {
|
|
34
42
|
const projectRoot = makeProjectRoot();
|
|
35
43
|
const core = openCore(projectRoot);
|
|
36
44
|
try {
|
|
37
|
-
expect(core.
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
expect(core.getOnboardingPolicy()).toEqual({
|
|
46
|
+
mode: 'auto_grant_free',
|
|
47
|
+
planVersionId: 'starter-v1'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
core.close();
|
|
52
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
it('falls back to require_plan_selection when no free onboarding plan is available', () => {
|
|
56
|
+
const projectRoot = makeProjectRoot();
|
|
57
|
+
const core = new TaskforceCore({
|
|
58
|
+
projectRoot,
|
|
59
|
+
storagePath: path.join(projectRoot, '.taskforce'),
|
|
60
|
+
tenantId: path.basename(projectRoot)
|
|
61
|
+
});
|
|
62
|
+
core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'active' });
|
|
63
|
+
core.updatePlanVersion({
|
|
64
|
+
planId: 'starter',
|
|
65
|
+
versionNumber: 1,
|
|
66
|
+
seatLimit: 1
|
|
67
|
+
});
|
|
68
|
+
core.updatePlanVersionFeatures({
|
|
69
|
+
planVersionId: 'starter-v1',
|
|
70
|
+
features: [
|
|
71
|
+
{ featureKey: 'workspace.switching', access: 'enabled' }
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
expect(core.getOnboardingPolicy()).toEqual({
|
|
76
|
+
mode: 'require_plan_selection',
|
|
77
|
+
planVersionId: null
|
|
40
78
|
});
|
|
41
79
|
}
|
|
42
80
|
finally {
|
|
@@ -44,16 +82,16 @@ describe('TaskforceCore signup monetization settings', () => {
|
|
|
44
82
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
45
83
|
}
|
|
46
84
|
});
|
|
47
|
-
it('fails validation for
|
|
85
|
+
it('fails validation for auto_grant_free without a plan version id', () => {
|
|
48
86
|
const projectRoot = makeProjectRoot();
|
|
49
87
|
const core = openCore(projectRoot);
|
|
50
88
|
try {
|
|
51
|
-
expect(core.
|
|
52
|
-
|
|
53
|
-
|
|
89
|
+
expect(core.validateOnboardingPolicy({
|
|
90
|
+
mode: 'auto_grant_free',
|
|
91
|
+
planVersionId: null
|
|
54
92
|
})).toEqual(expect.objectContaining({
|
|
55
93
|
valid: false,
|
|
56
|
-
code: '
|
|
94
|
+
code: 'ONBOARDING_PLAN_VERSION_REQUIRED'
|
|
57
95
|
}));
|
|
58
96
|
}
|
|
59
97
|
finally {
|
|
@@ -65,12 +103,12 @@ describe('TaskforceCore signup monetization settings', () => {
|
|
|
65
103
|
const projectRoot = makeProjectRoot();
|
|
66
104
|
const core = openCore(projectRoot);
|
|
67
105
|
try {
|
|
68
|
-
expect(core.
|
|
69
|
-
|
|
70
|
-
|
|
106
|
+
expect(core.validateOnboardingPolicy({
|
|
107
|
+
mode: 'auto_grant_free',
|
|
108
|
+
planVersionId: 'missing-v9'
|
|
71
109
|
})).toEqual(expect.objectContaining({
|
|
72
110
|
valid: false,
|
|
73
|
-
code: '
|
|
111
|
+
code: 'ONBOARDING_PLAN_VERSION_NOT_FOUND'
|
|
74
112
|
}));
|
|
75
113
|
}
|
|
76
114
|
finally {
|
|
@@ -83,12 +121,12 @@ describe('TaskforceCore signup monetization settings', () => {
|
|
|
83
121
|
const core = openCore(projectRoot);
|
|
84
122
|
try {
|
|
85
123
|
core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'archived' });
|
|
86
|
-
expect(core.
|
|
87
|
-
|
|
88
|
-
|
|
124
|
+
expect(core.validateOnboardingPolicy({
|
|
125
|
+
mode: 'auto_grant_free',
|
|
126
|
+
planVersionId: 'starter-v1'
|
|
89
127
|
})).toEqual(expect.objectContaining({
|
|
90
128
|
valid: false,
|
|
91
|
-
code: '
|
|
129
|
+
code: 'ONBOARDING_PLAN_NOT_ENABLED'
|
|
92
130
|
}));
|
|
93
131
|
}
|
|
94
132
|
finally {
|
|
@@ -96,13 +134,31 @@ describe('TaskforceCore signup monetization settings', () => {
|
|
|
96
134
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
97
135
|
}
|
|
98
136
|
});
|
|
99
|
-
it('
|
|
137
|
+
it('fails validation when auto_grant_free points at a version without free pricing', () => {
|
|
100
138
|
const projectRoot = makeProjectRoot();
|
|
101
139
|
const core = openCore(projectRoot);
|
|
102
140
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
core.upsertPlanCatalog({ planId: 'collab', displayName: 'Collab', status: 'active' });
|
|
142
|
+
expect(core.validateOnboardingPolicy({
|
|
143
|
+
mode: 'auto_grant_free',
|
|
144
|
+
planVersionId: 'collab-v1'
|
|
145
|
+
})).toEqual(expect.objectContaining({
|
|
146
|
+
valid: false,
|
|
147
|
+
code: 'ONBOARDING_PLAN_MUST_BE_FREE'
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
core.close();
|
|
152
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
it('passes validation for require_plan_selection without a plan version id', () => {
|
|
156
|
+
const projectRoot = makeProjectRoot();
|
|
157
|
+
const core = openCore(projectRoot);
|
|
158
|
+
try {
|
|
159
|
+
expect(core.validateOnboardingPolicy({
|
|
160
|
+
mode: 'require_plan_selection',
|
|
161
|
+
planVersionId: null
|
|
106
162
|
})).toEqual({ valid: true });
|
|
107
163
|
}
|
|
108
164
|
finally {
|
|
@@ -110,19 +166,21 @@ describe('TaskforceCore signup monetization settings', () => {
|
|
|
110
166
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
111
167
|
}
|
|
112
168
|
});
|
|
113
|
-
it('
|
|
169
|
+
it('allows backfill transitions to create pending plan selection entitlements', () => {
|
|
114
170
|
const projectRoot = makeProjectRoot();
|
|
115
171
|
const tenantId = path.basename(projectRoot);
|
|
116
|
-
const
|
|
172
|
+
const core = openCore(projectRoot);
|
|
117
173
|
try {
|
|
118
174
|
const now = new Date().toISOString();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
175
|
+
core.updateGlobalSettings({
|
|
176
|
+
billing: {
|
|
177
|
+
onboardingPolicy: {
|
|
178
|
+
mode: 'require_plan_selection',
|
|
179
|
+
planVersionId: null
|
|
180
|
+
}
|
|
123
181
|
}
|
|
124
182
|
});
|
|
125
|
-
const db =
|
|
183
|
+
const db = core.getDatabaseAdapter();
|
|
126
184
|
db.prepare(`
|
|
127
185
|
INSERT INTO users (id, tenant_id, email, display_name, auth_provider, external_auth_id, is_disabled, created_at, updated_at)
|
|
128
186
|
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
@@ -131,28 +189,129 @@ describe('TaskforceCore signup monetization settings', () => {
|
|
|
131
189
|
INSERT INTO user_billing (user_id, tenant_id, entitlement_state, created_at, updated_at)
|
|
132
190
|
VALUES (?, ?, ?, ?, ?)
|
|
133
191
|
`).run('pending-u1', tenantId, 'pending_plan_selection', now, now);
|
|
192
|
+
core.transitionCanonicalCommercialState({
|
|
193
|
+
accountId: 'pending-u1',
|
|
194
|
+
planVersionId: 'starter-v1',
|
|
195
|
+
state: 'pending_plan_selection',
|
|
196
|
+
source: 'backfill',
|
|
197
|
+
updatedBy: 'backfill',
|
|
198
|
+
billingMirrorPlanId: null,
|
|
199
|
+
effectiveAt: now,
|
|
200
|
+
createdAt: now,
|
|
201
|
+
updatedAt: now
|
|
202
|
+
});
|
|
203
|
+
expect(core.hasAccountEntitlement('pending-u1')).toBe(true);
|
|
204
|
+
expect(core.getAccountAccessStatus('pending-u1')).toEqual(expect.objectContaining({
|
|
205
|
+
gate: 'plan_selection_required',
|
|
206
|
+
canAccessApp: false,
|
|
207
|
+
canAccessPlans: true,
|
|
208
|
+
planSelectionRequired: true
|
|
209
|
+
}));
|
|
134
210
|
}
|
|
135
211
|
finally {
|
|
136
|
-
|
|
212
|
+
core.close();
|
|
213
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
137
214
|
}
|
|
138
|
-
|
|
215
|
+
});
|
|
216
|
+
it('treats a live checkout_pending entitlement as checkout recovery state', () => {
|
|
217
|
+
const projectRoot = makeProjectRoot();
|
|
218
|
+
const tenantId = path.basename(projectRoot);
|
|
219
|
+
const core = openCore(projectRoot);
|
|
139
220
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
221
|
+
const now = new Date().toISOString();
|
|
222
|
+
const checkoutExpiresAt = new Date(Date.now() + (60 * 60 * 1000)).toISOString();
|
|
223
|
+
core.upsertPlanCatalog({ planId: 'pro', displayName: 'Pro', status: 'active' });
|
|
224
|
+
const db = core.getDatabaseAdapter();
|
|
225
|
+
db.prepare(`
|
|
226
|
+
INSERT INTO users (id, tenant_id, email, display_name, auth_provider, external_auth_id, is_disabled, created_at, updated_at)
|
|
227
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
228
|
+
`).run('checkout-u1', tenantId, 'checkout@example.com', 'Checkout', 'token', 'checkout-u1', now, now);
|
|
229
|
+
core.transitionCanonicalCommercialState({
|
|
230
|
+
accountId: 'checkout-u1',
|
|
231
|
+
planVersionId: 'pro-v1',
|
|
232
|
+
state: 'checkout_pending',
|
|
233
|
+
source: 'checkout-start',
|
|
234
|
+
updatedBy: 'checkout-start',
|
|
235
|
+
billingMirrorPlanId: 'pro',
|
|
236
|
+
effectiveAt: now,
|
|
237
|
+
effectiveUntil: checkoutExpiresAt,
|
|
238
|
+
createdAt: now,
|
|
239
|
+
updatedAt: now
|
|
240
|
+
});
|
|
241
|
+
expect(core.getAccountAccessStatus('checkout-u1')).toEqual(expect.objectContaining({
|
|
242
|
+
gate: 'checkout_pending',
|
|
243
|
+
canAccessApp: false,
|
|
244
|
+
canAccessPlans: true,
|
|
245
|
+
planSelectionRequired: false,
|
|
246
|
+
message: 'Complete checkout to continue.'
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
core.close();
|
|
251
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
it('treats an expired checkout_pending entitlement as requiring plan reselection', () => {
|
|
255
|
+
const projectRoot = makeProjectRoot();
|
|
256
|
+
const tenantId = path.basename(projectRoot);
|
|
257
|
+
const core = openCore(projectRoot);
|
|
258
|
+
try {
|
|
259
|
+
const now = new Date().toISOString();
|
|
260
|
+
const expiredAt = new Date(Date.now() - (60 * 1000)).toISOString();
|
|
261
|
+
core.upsertPlanCatalog({ planId: 'pro', displayName: 'Pro', status: 'active' });
|
|
262
|
+
const db = core.getDatabaseAdapter();
|
|
263
|
+
db.prepare(`
|
|
264
|
+
INSERT INTO users (id, tenant_id, email, display_name, auth_provider, external_auth_id, is_disabled, created_at, updated_at)
|
|
265
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
266
|
+
`).run('expired-checkout-u1', tenantId, 'expired@example.com', 'Expired Checkout', 'token', 'expired-checkout-u1', now, now);
|
|
267
|
+
core.transitionCanonicalCommercialState({
|
|
268
|
+
accountId: 'expired-checkout-u1',
|
|
269
|
+
planVersionId: 'pro-v1',
|
|
270
|
+
state: 'checkout_pending',
|
|
271
|
+
source: 'checkout-start',
|
|
272
|
+
updatedBy: 'checkout-start',
|
|
273
|
+
billingMirrorPlanId: 'pro',
|
|
274
|
+
effectiveAt: now,
|
|
275
|
+
effectiveUntil: expiredAt,
|
|
276
|
+
createdAt: now,
|
|
277
|
+
updatedAt: now
|
|
145
278
|
});
|
|
146
|
-
expect(
|
|
147
|
-
expect(reopened.getAccountAccessStatus('pending-u1')).toEqual(expect.objectContaining({
|
|
279
|
+
expect(core.getAccountAccessStatus('expired-checkout-u1')).toEqual(expect.objectContaining({
|
|
148
280
|
gate: 'plan_selection_required',
|
|
149
281
|
canAccessApp: false,
|
|
150
282
|
canAccessPlans: true,
|
|
151
|
-
planSelectionRequired: true
|
|
283
|
+
planSelectionRequired: true,
|
|
284
|
+
message: 'Choose a plan to continue.'
|
|
285
|
+
}));
|
|
286
|
+
expect(core.getAccountEntitlement('expired-checkout-u1')).toEqual(expect.objectContaining({
|
|
287
|
+
state: 'pending_plan_selection',
|
|
288
|
+
effectiveUntil: null
|
|
289
|
+
}));
|
|
290
|
+
const entitlementRow = db.prepare(`
|
|
291
|
+
SELECT entitlement_state, effective_until, updated_by
|
|
292
|
+
FROM account_entitlements
|
|
293
|
+
WHERE tenant_id = ? AND account_id = ?
|
|
294
|
+
LIMIT 1
|
|
295
|
+
`).get(tenantId, 'expired-checkout-u1');
|
|
296
|
+
expect(entitlementRow).toEqual(expect.objectContaining({
|
|
297
|
+
entitlement_state: 'pending_plan_selection',
|
|
298
|
+
effective_until: null,
|
|
299
|
+
updated_by: 'checkout-expired'
|
|
300
|
+
}));
|
|
301
|
+
const billingRow = db.prepare(`
|
|
302
|
+
SELECT entitlement_state, tier, effective_until
|
|
303
|
+
FROM user_billing
|
|
304
|
+
WHERE tenant_id = ? AND user_id = ?
|
|
305
|
+
LIMIT 1
|
|
306
|
+
`).get(tenantId, 'expired-checkout-u1');
|
|
307
|
+
expect(billingRow).toEqual(expect.objectContaining({
|
|
308
|
+
entitlement_state: 'pending_plan_selection',
|
|
309
|
+
tier: '',
|
|
310
|
+
effective_until: null
|
|
152
311
|
}));
|
|
153
312
|
}
|
|
154
313
|
finally {
|
|
155
|
-
|
|
314
|
+
core.close();
|
|
156
315
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
157
316
|
}
|
|
158
317
|
});
|