@open-mercato/core 0.4.2-canary-15e78de280 → 0.4.2-canary-f075c3eb92
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/modules/api_keys/setup.js +11 -0
- package/dist/modules/api_keys/setup.js.map +7 -0
- package/dist/modules/attachments/components/AttachmentLibrary.js +1 -1
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/lib/assignmentDetails.js +31 -17
- package/dist/modules/attachments/lib/assignmentDetails.js.map +2 -2
- package/dist/modules/attachments/lib/partitions.js +3 -3
- package/dist/modules/attachments/lib/partitions.js.map +2 -2
- package/dist/modules/attachments/setup.js +11 -0
- package/dist/modules/attachments/setup.js.map +7 -0
- package/dist/modules/audit_logs/setup.js +12 -0
- package/dist/modules/audit_logs/setup.js.map +7 -0
- package/dist/modules/auth/lib/setup-app.js +29 -159
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/setup.js +11 -0
- package/dist/modules/auth/setup.js.map +7 -0
- package/dist/modules/business_rules/setup.js +11 -0
- package/dist/modules/business_rules/setup.js.map +7 -0
- package/dist/modules/catalog/setup.js +22 -0
- package/dist/modules/catalog/setup.js.map +7 -0
- package/dist/modules/configs/lib/upgrade-actions.js +65 -15
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/configs/setup.js +16 -0
- package/dist/modules/configs/setup.js.map +7 -0
- package/dist/modules/currencies/setup.js +16 -0
- package/dist/modules/currencies/setup.js.map +7 -0
- package/dist/modules/customers/setup.js +36 -0
- package/dist/modules/customers/setup.js.map +7 -0
- package/dist/modules/dashboards/setup.js +12 -0
- package/dist/modules/dashboards/setup.js.map +7 -0
- package/dist/modules/dictionaries/setup.js +12 -0
- package/dist/modules/dictionaries/setup.js.map +7 -0
- package/dist/modules/directory/setup.js +12 -0
- package/dist/modules/directory/setup.js.map +7 -0
- package/dist/modules/entities/setup.js +11 -0
- package/dist/modules/entities/setup.js.map +7 -0
- package/dist/modules/feature_toggles/setup.js +11 -0
- package/dist/modules/feature_toggles/setup.js.map +7 -0
- package/dist/modules/perspectives/setup.js +12 -0
- package/dist/modules/perspectives/setup.js.map +7 -0
- package/dist/modules/planner/setup.js +21 -0
- package/dist/modules/planner/setup.js.map +7 -0
- package/dist/modules/query_index/setup.js +11 -0
- package/dist/modules/query_index/setup.js.map +7 -0
- package/dist/modules/resources/setup.js +21 -0
- package/dist/modules/resources/setup.js.map +7 -0
- package/dist/modules/sales/setup.js +99 -0
- package/dist/modules/sales/setup.js.map +7 -0
- package/dist/modules/staff/setup.js +27 -0
- package/dist/modules/staff/setup.js.map +7 -0
- package/dist/modules/workflows/lib/seeds.js +3 -15
- package/dist/modules/workflows/lib/seeds.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20251207131955.js +76 -72
- package/dist/modules/workflows/migrations/Migration20251207131955.js.map +2 -2
- package/dist/modules/workflows/setup.js +16 -0
- package/dist/modules/workflows/setup.js.map +7 -0
- package/package.json +2 -2
- package/src/__tests__/module-decoupling.test.ts +356 -0
- package/src/modules/api_keys/setup.ts +9 -0
- package/src/modules/attachments/components/AttachmentLibrary.tsx +2 -2
- package/src/modules/attachments/lib/assignmentDetails.ts +32 -16
- package/src/modules/attachments/lib/partitions.ts +3 -3
- package/src/modules/attachments/setup.ts +9 -0
- package/src/modules/audit_logs/setup.ts +10 -0
- package/src/modules/auth/__tests__/cli-setup-acl.test.ts +30 -0
- package/src/modules/auth/lib/setup-app.ts +40 -177
- package/src/modules/auth/setup.ts +9 -0
- package/src/modules/business_rules/setup.ts +9 -0
- package/src/modules/catalog/setup.ts +22 -0
- package/src/modules/configs/lib/upgrade-actions.ts +78 -17
- package/src/modules/configs/setup.ts +14 -0
- package/src/modules/currencies/setup.ts +15 -0
- package/src/modules/customers/setup.ts +36 -0
- package/src/modules/dashboards/setup.ts +10 -0
- package/src/modules/dictionaries/setup.ts +10 -0
- package/src/modules/directory/setup.ts +10 -0
- package/src/modules/entities/setup.ts +9 -0
- package/src/modules/feature_toggles/setup.ts +9 -0
- package/src/modules/perspectives/setup.ts +10 -0
- package/src/modules/planner/setup.ts +21 -0
- package/src/modules/query_index/setup.ts +9 -0
- package/src/modules/resources/setup.ts +21 -0
- package/src/modules/sales/setup.ts +108 -0
- package/src/modules/staff/setup.ts +27 -0
- package/src/modules/workflows/lib/seeds.ts +4 -16
- package/src/modules/workflows/migrations/Migration20251207131955.ts +77 -143
- package/src/modules/workflows/setup.ts +15 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { seedExampleWorkflows } from "./lib/seeds.js";
|
|
2
|
+
const setup = {
|
|
3
|
+
seedDefaults: async (ctx) => {
|
|
4
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
5
|
+
await seedExampleWorkflows(ctx.em, scope);
|
|
6
|
+
},
|
|
7
|
+
defaultRoleFeatures: {
|
|
8
|
+
admin: ["workflows.*"]
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var setup_default = setup;
|
|
12
|
+
export {
|
|
13
|
+
setup_default as default,
|
|
14
|
+
setup
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/workflows/setup.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport { seedExampleWorkflows } from './lib/seeds'\n\nexport const setup: ModuleSetupConfig = {\n seedDefaults: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedExampleWorkflows(ctx.em, scope)\n },\n\n defaultRoleFeatures: {\n admin: ['workflows.*'],\n },\n}\n\nexport default setup\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,4BAA4B;AAE9B,MAAM,QAA2B;AAAA,EACtC,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,qBAAqB,IAAI,IAAI,KAAK;AAAA,EAC1C;AAAA,EAEA,qBAAqB;AAAA,IACnB,OAAO,CAAC,aAAa;AAAA,EACvB;AACF;AAEA,IAAO,gBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.2-canary-
|
|
3
|
+
"version": "0.4.2-canary-f075c3eb92",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.2-canary-
|
|
210
|
+
"@open-mercato/shared": "0.4.2-canary-f075c3eb92",
|
|
211
211
|
"@xyflow/react": "^12.6.0",
|
|
212
212
|
"date-fns": "^4.1.0",
|
|
213
213
|
"date-fns-tz": "^3.2.0"
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Decoupling Tests
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the application remains functional when optional modules
|
|
5
|
+
* (catalog, sales, api_keys) are disabled — removed from the generated
|
|
6
|
+
* module registry and entity IDs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Build a reduced entity IDs registry excluding catalog, sales, api_keys
|
|
10
|
+
const reducedE = {
|
|
11
|
+
dashboards: {
|
|
12
|
+
dashboard_layout: 'dashboards:dashboard_layout',
|
|
13
|
+
dashboard_role_widgets: 'dashboards:dashboard_role_widgets',
|
|
14
|
+
dashboard_user_widgets: 'dashboards:dashboard_user_widgets',
|
|
15
|
+
},
|
|
16
|
+
auth: {
|
|
17
|
+
password_reset: 'auth:password_reset',
|
|
18
|
+
role: 'auth:role',
|
|
19
|
+
role_acl: 'auth:role_acl',
|
|
20
|
+
role_sidebar_preference: 'auth:role_sidebar_preference',
|
|
21
|
+
session: 'auth:session',
|
|
22
|
+
user: 'auth:user',
|
|
23
|
+
user_acl: 'auth:user_acl',
|
|
24
|
+
user_role: 'auth:user_role',
|
|
25
|
+
user_sidebar_preference: 'auth:user_sidebar_preference',
|
|
26
|
+
},
|
|
27
|
+
directory: {
|
|
28
|
+
organization: 'directory:organization',
|
|
29
|
+
tenant: 'directory:tenant',
|
|
30
|
+
},
|
|
31
|
+
customers: {
|
|
32
|
+
customer_activity: 'customers:customer_activity',
|
|
33
|
+
customer_address: 'customers:customer_address',
|
|
34
|
+
customer_comment: 'customers:customer_comment',
|
|
35
|
+
customer_company_profile: 'customers:customer_company_profile',
|
|
36
|
+
customer_deal: 'customers:customer_deal',
|
|
37
|
+
customer_deal_company_link: 'customers:customer_deal_company_link',
|
|
38
|
+
customer_deal_person_link: 'customers:customer_deal_person_link',
|
|
39
|
+
customer_dictionary_entry: 'customers:customer_dictionary_entry',
|
|
40
|
+
customer_entity: 'customers:customer_entity',
|
|
41
|
+
customer_person_profile: 'customers:customer_person_profile',
|
|
42
|
+
customer_settings: 'customers:customer_settings',
|
|
43
|
+
customer_tag: 'customers:customer_tag',
|
|
44
|
+
customer_tag_assignment: 'customers:customer_tag_assignment',
|
|
45
|
+
customer_todo_link: 'customers:customer_todo_link',
|
|
46
|
+
},
|
|
47
|
+
entities: {
|
|
48
|
+
custom_entity: 'entities:custom_entity',
|
|
49
|
+
custom_entity_storage: 'entities:custom_entity_storage',
|
|
50
|
+
custom_field_def: 'entities:custom_field_def',
|
|
51
|
+
custom_field_entity_config: 'entities:custom_field_entity_config',
|
|
52
|
+
custom_field_value: 'entities:custom_field_value',
|
|
53
|
+
encryption_map: 'entities:encryption_map',
|
|
54
|
+
},
|
|
55
|
+
attachments: {
|
|
56
|
+
attachment: 'attachments:attachment',
|
|
57
|
+
attachment_partition: 'attachments:attachment_partition',
|
|
58
|
+
},
|
|
59
|
+
} as const
|
|
60
|
+
|
|
61
|
+
const reducedM = {
|
|
62
|
+
dashboards: 'dashboards',
|
|
63
|
+
auth: 'auth',
|
|
64
|
+
directory: 'directory',
|
|
65
|
+
customers: 'customers',
|
|
66
|
+
entities: 'entities',
|
|
67
|
+
attachments: 'attachments',
|
|
68
|
+
} as const
|
|
69
|
+
|
|
70
|
+
// Mock generated entity IDs to exclude catalog, sales, api_keys
|
|
71
|
+
jest.mock('#generated/entities.ids.generated', () => ({
|
|
72
|
+
E: reducedE,
|
|
73
|
+
M: reducedM,
|
|
74
|
+
}))
|
|
75
|
+
|
|
76
|
+
// Mock cache dependency used by upgrade-actions
|
|
77
|
+
jest.mock('@open-mercato/cache', () => ({
|
|
78
|
+
runWithCacheTenant: jest.fn((_: unknown, fn: () => unknown) => fn()),
|
|
79
|
+
}))
|
|
80
|
+
jest.mock('@open-mercato/shared/lib/crud/cache-stats', () => ({
|
|
81
|
+
collectCrudCacheStats: jest.fn().mockResolvedValue({ segments: [] }),
|
|
82
|
+
purgeCrudCacheSegment: jest.fn(),
|
|
83
|
+
}))
|
|
84
|
+
jest.mock('@open-mercato/shared/lib/crud/cache', () => ({
|
|
85
|
+
isCrudCacheEnabled: jest.fn().mockReturnValue(false),
|
|
86
|
+
resolveCrudCache: jest.fn(),
|
|
87
|
+
}))
|
|
88
|
+
|
|
89
|
+
import { registerEntityIds, getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'
|
|
90
|
+
import { registerModules } from '@open-mercato/shared/lib/modules/registry'
|
|
91
|
+
import type { Module } from '@open-mercato/shared/modules/registry'
|
|
92
|
+
import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
93
|
+
|
|
94
|
+
// Build module list that mirrors enabled modules without catalog/sales/api_keys
|
|
95
|
+
function buildReducedModules(): Module[] {
|
|
96
|
+
const moduleSetups: Record<string, ModuleSetupConfig> = {
|
|
97
|
+
dashboards: {
|
|
98
|
+
defaultRoleFeatures: {
|
|
99
|
+
admin: ['dashboards.*', 'dashboards.admin.assign-widgets'],
|
|
100
|
+
employee: ['dashboards.view', 'dashboards.configure'],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
auth: {
|
|
104
|
+
defaultRoleFeatures: { admin: ['auth.*'] },
|
|
105
|
+
},
|
|
106
|
+
directory: {
|
|
107
|
+
defaultRoleFeatures: {
|
|
108
|
+
superadmin: ['directory.tenants.*'],
|
|
109
|
+
admin: ['directory.organizations.view', 'directory.organizations.manage'],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
customers: {
|
|
113
|
+
defaultRoleFeatures: {
|
|
114
|
+
admin: [
|
|
115
|
+
'customers.*',
|
|
116
|
+
'customers.people.view',
|
|
117
|
+
'customers.people.manage',
|
|
118
|
+
'customers.companies.view',
|
|
119
|
+
'customers.companies.manage',
|
|
120
|
+
'customers.deals.view',
|
|
121
|
+
'customers.deals.manage',
|
|
122
|
+
],
|
|
123
|
+
employee: [
|
|
124
|
+
'customers.*',
|
|
125
|
+
'customers.people.view',
|
|
126
|
+
'customers.people.manage',
|
|
127
|
+
'customers.companies.view',
|
|
128
|
+
'customers.companies.manage',
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
entities: {
|
|
133
|
+
defaultRoleFeatures: { admin: ['entities.*'] },
|
|
134
|
+
},
|
|
135
|
+
attachments: {
|
|
136
|
+
defaultRoleFeatures: { admin: ['attachments.*', 'attachments.view', 'attachments.manage'] },
|
|
137
|
+
},
|
|
138
|
+
audit_logs: {
|
|
139
|
+
defaultRoleFeatures: { admin: ['audit_logs.*'], employee: ['audit_logs.undo_self'] },
|
|
140
|
+
},
|
|
141
|
+
dictionaries: {
|
|
142
|
+
defaultRoleFeatures: { admin: ['dictionaries.view', 'dictionaries.manage'], employee: ['dictionaries.view'] },
|
|
143
|
+
},
|
|
144
|
+
perspectives: {
|
|
145
|
+
defaultRoleFeatures: { admin: ['perspectives.use', 'perspectives.role_defaults'], employee: ['perspectives.use'] },
|
|
146
|
+
},
|
|
147
|
+
configs: {
|
|
148
|
+
defaultRoleFeatures: { admin: ['configs.system_status.view', 'configs.cache.view', 'configs.cache.manage', 'configs.manage'] },
|
|
149
|
+
},
|
|
150
|
+
query_index: {
|
|
151
|
+
defaultRoleFeatures: { admin: ['query_index.*'] },
|
|
152
|
+
},
|
|
153
|
+
feature_toggles: {
|
|
154
|
+
defaultRoleFeatures: { admin: ['feature_toggles.*'] },
|
|
155
|
+
},
|
|
156
|
+
business_rules: {
|
|
157
|
+
defaultRoleFeatures: { admin: ['business_rules.*'] },
|
|
158
|
+
},
|
|
159
|
+
workflows: {
|
|
160
|
+
defaultRoleFeatures: { admin: ['workflows.*'] },
|
|
161
|
+
},
|
|
162
|
+
currencies: {
|
|
163
|
+
defaultRoleFeatures: { admin: ['currencies.*'] },
|
|
164
|
+
},
|
|
165
|
+
staff: {
|
|
166
|
+
defaultRoleFeatures: {
|
|
167
|
+
admin: ['staff.*', 'staff.leave_requests.manage'],
|
|
168
|
+
employee: [
|
|
169
|
+
'staff.leave_requests.send',
|
|
170
|
+
'staff.my_availability.view',
|
|
171
|
+
'staff.my_availability.manage',
|
|
172
|
+
'staff.my_leave_requests.view',
|
|
173
|
+
'staff.my_leave_requests.send',
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
resources: {
|
|
178
|
+
defaultRoleFeatures: { admin: ['resources.*'] },
|
|
179
|
+
},
|
|
180
|
+
planner: {
|
|
181
|
+
defaultRoleFeatures: { admin: ['planner.*'], employee: ['planner.view'] },
|
|
182
|
+
},
|
|
183
|
+
search: {
|
|
184
|
+
defaultRoleFeatures: { admin: ['search.*', 'vector.*'], employee: ['vector.*'] },
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return Object.entries(moduleSetups).map(([id, setup]) => ({
|
|
189
|
+
id,
|
|
190
|
+
setup,
|
|
191
|
+
}))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const reducedModules = buildReducedModules()
|
|
195
|
+
|
|
196
|
+
beforeAll(() => {
|
|
197
|
+
registerEntityIds(reducedE as any)
|
|
198
|
+
registerModules(reducedModules)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('Module Decoupling', () => {
|
|
202
|
+
describe('1. Entity IDs registry excludes disabled modules', () => {
|
|
203
|
+
it('getEntityIds() has no catalog, sales, or api_keys keys', () => {
|
|
204
|
+
const entityIds = getEntityIds()
|
|
205
|
+
expect(entityIds).not.toHaveProperty('catalog')
|
|
206
|
+
expect(entityIds).not.toHaveProperty('sales')
|
|
207
|
+
expect(entityIds).not.toHaveProperty('api_keys')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('still contains core modules', () => {
|
|
211
|
+
const entityIds = getEntityIds()
|
|
212
|
+
expect(entityIds).toHaveProperty('auth')
|
|
213
|
+
expect(entityIds).toHaveProperty('attachments')
|
|
214
|
+
expect(entityIds).toHaveProperty('customers')
|
|
215
|
+
expect(entityIds).toHaveProperty('dashboards')
|
|
216
|
+
expect(entityIds).toHaveProperty('directory')
|
|
217
|
+
expect(entityIds).toHaveProperty('entities')
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe('2. attachments/partitions.ts — resolveDefaultPartitionCode', () => {
|
|
222
|
+
it('returns privateAttachments for null, undefined, arbitrary strings, and catalog entity IDs', async () => {
|
|
223
|
+
const { resolveDefaultPartitionCode } = await import(
|
|
224
|
+
'@open-mercato/core/modules/attachments/lib/partitions'
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
expect(resolveDefaultPartitionCode(null)).toBe('privateAttachments')
|
|
228
|
+
expect(resolveDefaultPartitionCode(undefined)).toBe('privateAttachments')
|
|
229
|
+
expect(resolveDefaultPartitionCode('some-entity')).toBe('privateAttachments')
|
|
230
|
+
// When catalog is disabled, the literal string also falls through
|
|
231
|
+
expect(resolveDefaultPartitionCode('catalog:catalog_product')).toBe('privateAttachments')
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
describe('3. attachments/assignmentDetails.ts — resolveAssignmentEnrichments', () => {
|
|
236
|
+
it('does not crash with empty assignments and null queryEngine', async () => {
|
|
237
|
+
const { resolveAssignmentEnrichments } = await import(
|
|
238
|
+
'@open-mercato/core/modules/attachments/lib/assignmentDetails'
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const result = await resolveAssignmentEnrichments([], {
|
|
242
|
+
queryEngine: null,
|
|
243
|
+
tenantId: 'tenant-1',
|
|
244
|
+
organizationId: 'org-1',
|
|
245
|
+
})
|
|
246
|
+
expect(result).toBeInstanceOf(Map)
|
|
247
|
+
expect(result.size).toBe(0)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('does not crash with assignments referencing catalog entity IDs', async () => {
|
|
251
|
+
const { resolveAssignmentEnrichments } = await import(
|
|
252
|
+
'@open-mercato/core/modules/attachments/lib/assignmentDetails'
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
const result = await resolveAssignmentEnrichments(
|
|
256
|
+
[{ type: 'catalog:catalog_product', id: 'some-uuid' }],
|
|
257
|
+
{
|
|
258
|
+
queryEngine: null,
|
|
259
|
+
tenantId: 'tenant-1',
|
|
260
|
+
organizationId: 'org-1',
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
expect(result).toBeInstanceOf(Map)
|
|
264
|
+
expect(result.size).toBe(0)
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
describe('4. Role feature merging', () => {
|
|
269
|
+
it('admin features contain enabled module features', () => {
|
|
270
|
+
const adminFeatures: string[] = []
|
|
271
|
+
for (const mod of reducedModules) {
|
|
272
|
+
const roleFeatures = mod.setup?.defaultRoleFeatures
|
|
273
|
+
if (roleFeatures?.admin) adminFeatures.push(...roleFeatures.admin)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
expect(adminFeatures).toContain('auth.*')
|
|
277
|
+
expect(adminFeatures).toContain('attachments.*')
|
|
278
|
+
expect(adminFeatures).toContain('dashboards.*')
|
|
279
|
+
expect(adminFeatures).toContain('customers.*')
|
|
280
|
+
expect(adminFeatures).toContain('entities.*')
|
|
281
|
+
expect(adminFeatures).toContain('query_index.*')
|
|
282
|
+
expect(adminFeatures).toContain('configs.manage')
|
|
283
|
+
expect(adminFeatures).toContain('directory.organizations.manage')
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('admin features do NOT contain disabled module features', () => {
|
|
287
|
+
const adminFeatures: string[] = []
|
|
288
|
+
for (const mod of reducedModules) {
|
|
289
|
+
const roleFeatures = mod.setup?.defaultRoleFeatures
|
|
290
|
+
if (roleFeatures?.admin) adminFeatures.push(...roleFeatures.admin)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
expect(adminFeatures).not.toContain('catalog.*')
|
|
294
|
+
expect(adminFeatures).not.toContain('sales.*')
|
|
295
|
+
expect(adminFeatures).not.toContain('api_keys.*')
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('employee features similarly exclude disabled module features', () => {
|
|
299
|
+
const employeeFeatures: string[] = []
|
|
300
|
+
for (const mod of reducedModules) {
|
|
301
|
+
const roleFeatures = mod.setup?.defaultRoleFeatures
|
|
302
|
+
if (roleFeatures?.employee) employeeFeatures.push(...roleFeatures.employee)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
expect(employeeFeatures).not.toContain('catalog.*')
|
|
306
|
+
expect(employeeFeatures).not.toContain('sales.*')
|
|
307
|
+
expect(employeeFeatures).not.toContain('api_keys.*')
|
|
308
|
+
|
|
309
|
+
// Enabled modules' employee features are present
|
|
310
|
+
expect(employeeFeatures).toContain('customers.*')
|
|
311
|
+
expect(employeeFeatures).toContain('dashboards.view')
|
|
312
|
+
expect(employeeFeatures).toContain('audit_logs.undo_self')
|
|
313
|
+
expect(employeeFeatures).toContain('vector.*')
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('superadmin features come from enabled modules only', () => {
|
|
317
|
+
const superadminFeatures: string[] = []
|
|
318
|
+
for (const mod of reducedModules) {
|
|
319
|
+
const roleFeatures = mod.setup?.defaultRoleFeatures
|
|
320
|
+
if (roleFeatures?.superadmin) superadminFeatures.push(...roleFeatures.superadmin)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
expect(superadminFeatures).toContain('directory.tenants.*')
|
|
324
|
+
expect(superadminFeatures).not.toContain('catalog.*')
|
|
325
|
+
expect(superadminFeatures).not.toContain('sales.*')
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
describe('5. configs/upgrade-actions.ts loads without crashing', () => {
|
|
330
|
+
it('imports and exposes upgradeActions array and compareVersions function', async () => {
|
|
331
|
+
const mod = await import(
|
|
332
|
+
'@open-mercato/core/modules/configs/lib/upgrade-actions'
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
expect(Array.isArray(mod.upgradeActions)).toBe(true)
|
|
336
|
+
expect(typeof mod.compareVersions).toBe('function')
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('upgradeActions array is defined and non-empty', async () => {
|
|
340
|
+
const mod = await import(
|
|
341
|
+
'@open-mercato/core/modules/configs/lib/upgrade-actions'
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
expect(mod.upgradeActions.length).toBeGreaterThan(0)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('actionsUpToVersion returns actions without crashing', async () => {
|
|
348
|
+
const mod = await import(
|
|
349
|
+
'@open-mercato/core/modules/configs/lib/upgrade-actions'
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
const actions = mod.actionsUpToVersion('99.99.99')
|
|
353
|
+
expect(Array.isArray(actions)).toBe(true)
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
})
|
|
@@ -893,8 +893,8 @@ export function AttachmentLibrary() {
|
|
|
893
893
|
{assignments.map((assignment) => {
|
|
894
894
|
const label = assignment.label?.trim() || assignment.id
|
|
895
895
|
const hideType =
|
|
896
|
-
assignment.type === E.catalog
|
|
897
|
-
assignment.type === E.catalog
|
|
896
|
+
assignment.type === (E as any).catalog?.catalog_product ||
|
|
897
|
+
assignment.type === (E as any).catalog?.catalog_product_variant
|
|
898
898
|
const content = hideType ? label : `${assignment.type}: ${label}`
|
|
899
899
|
return assignment.href ? (
|
|
900
900
|
<a
|
|
@@ -15,12 +15,16 @@ let _entityLinkSpecsCache: Record<string, AssignmentLinkSpec> | null = null
|
|
|
15
15
|
function getEntityLinkSpecs(): Record<string, AssignmentLinkSpec> {
|
|
16
16
|
if (_entityLinkSpecsCache) return _entityLinkSpecsCache
|
|
17
17
|
const E = getEntityIds() as any
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const specs: Record<string, AssignmentLinkSpec> = {}
|
|
19
|
+
|
|
20
|
+
if (E.catalog?.catalog_product) {
|
|
21
|
+
specs[E.catalog.catalog_product] = {
|
|
20
22
|
labelFields: ['title', 'sku', 'handle'],
|
|
21
23
|
buildHref: (record) => buildSimpleHref('/backend/catalog/products', record.id),
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (E.catalog?.catalog_product_variant) {
|
|
27
|
+
specs[E.catalog.catalog_product_variant] = {
|
|
24
28
|
labelFields: ['name', 'sku'],
|
|
25
29
|
extraFields: ['product_id'],
|
|
26
30
|
buildHref: (record) => {
|
|
@@ -28,8 +32,10 @@ function getEntityLinkSpecs(): Record<string, AssignmentLinkSpec> {
|
|
|
28
32
|
if (!productId) return null
|
|
29
33
|
return `/backend/catalog/products/${encodeURIComponent(productId)}/variants/${encodeURIComponent(String(record.id ?? ''))}`
|
|
30
34
|
},
|
|
31
|
-
}
|
|
32
|
-
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (E.customers?.customer_entity) {
|
|
38
|
+
specs[E.customers.customer_entity] = {
|
|
33
39
|
labelFields: ['display_name'],
|
|
34
40
|
extraFields: ['kind'],
|
|
35
41
|
buildHref: (record) => {
|
|
@@ -38,32 +44,42 @@ function getEntityLinkSpecs(): Record<string, AssignmentLinkSpec> {
|
|
|
38
44
|
if (kind === 'person') return buildSimpleHref('/backend/customers/people', record.id)
|
|
39
45
|
return null
|
|
40
46
|
},
|
|
41
|
-
}
|
|
42
|
-
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (E.customers?.customer_person_profile) {
|
|
50
|
+
specs[E.customers.customer_person_profile] = {
|
|
43
51
|
labelFields: ['preferred_name', 'display_name', 'first_name', 'last_name'],
|
|
44
52
|
extraFields: ['entity_id', 'first_name', 'last_name'],
|
|
45
53
|
buildHref: (record) => {
|
|
46
54
|
const entityId = readRecordValue(record, 'entity_id')
|
|
47
55
|
return entityId ? buildSimpleHref('/backend/customers/people', entityId) : null
|
|
48
56
|
},
|
|
49
|
-
}
|
|
50
|
-
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (E.customers?.customer_company_profile) {
|
|
60
|
+
specs[E.customers.customer_company_profile] = {
|
|
51
61
|
labelFields: ['brand_name', 'display_name', 'legal_name'],
|
|
52
62
|
extraFields: ['entity_id'],
|
|
53
63
|
buildHref: (record) => {
|
|
54
64
|
const entityId = readRecordValue(record, 'entity_id')
|
|
55
65
|
return entityId ? buildSimpleHref('/backend/customers/companies', entityId) : null
|
|
56
66
|
},
|
|
57
|
-
}
|
|
58
|
-
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (E.customers?.customer_deal) {
|
|
70
|
+
specs[E.customers.customer_deal] = {
|
|
59
71
|
labelFields: ['title'],
|
|
60
72
|
buildHref: (record) => buildSimpleHref('/backend/customers/deals', record.id),
|
|
61
|
-
}
|
|
62
|
-
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (E.sales?.sales_channel) {
|
|
76
|
+
specs[E.sales.sales_channel] = {
|
|
63
77
|
labelFields: ['name', 'title'],
|
|
64
78
|
buildHref: (record) => buildSimpleHref('/backend/sales/channels', record.id, '/edit'),
|
|
65
|
-
}
|
|
79
|
+
}
|
|
66
80
|
}
|
|
81
|
+
|
|
82
|
+
_entityLinkSpecsCache = specs
|
|
67
83
|
return _entityLinkSpecsCache
|
|
68
84
|
}
|
|
69
85
|
|
|
@@ -161,7 +177,7 @@ function isUuid(value: string | null | undefined): boolean {
|
|
|
161
177
|
|
|
162
178
|
function filterIdsForEntity(entityId: string, ids: string[]): string[] {
|
|
163
179
|
const E = getEntityIds() as any
|
|
164
|
-
if (entityId === E.catalog
|
|
180
|
+
if (entityId === E.catalog?.catalog_product_variant || entityId === E.catalog?.catalog_product) {
|
|
165
181
|
return ids.filter((id) => isUuid(id))
|
|
166
182
|
}
|
|
167
183
|
return ids
|
|
@@ -26,9 +26,9 @@ export const DEFAULT_ATTACHMENT_PARTITIONS: AttachmentPartitionSeed[] = [
|
|
|
26
26
|
},
|
|
27
27
|
]
|
|
28
28
|
|
|
29
|
-
const PRODUCT_MEDIA_ENTITY_IDS = new Set<string>(
|
|
30
|
-
E.catalog.
|
|
31
|
-
|
|
29
|
+
const PRODUCT_MEDIA_ENTITY_IDS = new Set<string>(
|
|
30
|
+
[(E as any).catalog?.catalog_product].filter(Boolean) as string[]
|
|
31
|
+
)
|
|
32
32
|
|
|
33
33
|
const FALLBACK_PARTITION = 'privateAttachments'
|
|
34
34
|
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
/** @jest-environment node */
|
|
2
|
+
import { registerModules } from '@open-mercato/shared/lib/modules/registry'
|
|
3
|
+
import type { Module } from '@open-mercato/shared/modules/registry'
|
|
2
4
|
import cli from '@open-mercato/core/modules/auth/cli'
|
|
3
5
|
|
|
6
|
+
// Register modules so that ensureDefaultRoleAcls can read defaultRoleFeatures
|
|
7
|
+
const testModules: Module[] = [
|
|
8
|
+
{ id: 'auth', setup: { defaultRoleFeatures: { admin: ['auth.*'] } } },
|
|
9
|
+
{ id: 'entities', setup: { defaultRoleFeatures: { admin: ['entities.*'] } } },
|
|
10
|
+
{ id: 'attachments', setup: { defaultRoleFeatures: { admin: ['attachments.*', 'attachments.view', 'attachments.manage'] } } },
|
|
11
|
+
{ id: 'query_index', setup: { defaultRoleFeatures: { admin: ['query_index.*'] } } },
|
|
12
|
+
{ id: 'configs', setup: { defaultRoleFeatures: { admin: ['configs.system_status.view', 'configs.cache.view', 'configs.cache.manage', 'configs.manage'] } } },
|
|
13
|
+
{ id: 'directory', setup: { defaultRoleFeatures: { superadmin: ['directory.tenants.*'], admin: ['directory.organizations.view', 'directory.organizations.manage'] } } },
|
|
14
|
+
{ id: 'customers', setup: { defaultRoleFeatures: { admin: ['customers.*', 'customers.people.view', 'customers.people.manage', 'customers.companies.view', 'customers.companies.manage', 'customers.deals.view', 'customers.deals.manage'], employee: ['customers.*', 'customers.people.view', 'customers.people.manage', 'customers.companies.view', 'customers.companies.manage'] } } },
|
|
15
|
+
{ id: 'catalog', setup: { defaultRoleFeatures: { admin: ['catalog.*', 'catalog.variants.manage', 'catalog.pricing.manage'], employee: ['catalog.*', 'catalog.variants.manage', 'catalog.pricing.manage'] } } },
|
|
16
|
+
{ id: 'sales', setup: { defaultRoleFeatures: { admin: ['sales.*'], employee: ['sales.*'] } } },
|
|
17
|
+
{ id: 'dictionaries', setup: { defaultRoleFeatures: { admin: ['dictionaries.view', 'dictionaries.manage'], employee: ['dictionaries.view'] } } },
|
|
18
|
+
{ id: 'audit_logs', setup: { defaultRoleFeatures: { admin: ['audit_logs.*'], employee: ['audit_logs.undo_self'] } } },
|
|
19
|
+
{ id: 'dashboards', setup: { defaultRoleFeatures: { admin: ['dashboards.*', 'dashboards.admin.assign-widgets'], employee: ['dashboards.view', 'dashboards.configure'] } } },
|
|
20
|
+
{ id: 'api_keys', setup: { defaultRoleFeatures: { admin: ['api_keys.*'] } } },
|
|
21
|
+
{ id: 'perspectives', setup: { defaultRoleFeatures: { admin: ['perspectives.use', 'perspectives.role_defaults'], employee: ['perspectives.use'] } } },
|
|
22
|
+
{ id: 'feature_toggles', setup: { defaultRoleFeatures: { admin: ['feature_toggles.*'] } } },
|
|
23
|
+
{ id: 'business_rules', setup: { defaultRoleFeatures: { admin: ['business_rules.*'] } } },
|
|
24
|
+
{ id: 'workflows', setup: { defaultRoleFeatures: { admin: ['workflows.*'] } } },
|
|
25
|
+
{ id: 'search', setup: { defaultRoleFeatures: { admin: ['search.*', 'vector.*'], employee: ['vector.*'] } } },
|
|
26
|
+
{ id: 'currencies', setup: { defaultRoleFeatures: { admin: ['currencies.*'] } } },
|
|
27
|
+
{ id: 'planner', setup: { defaultRoleFeatures: { admin: ['planner.*'], employee: ['planner.view'] } } },
|
|
28
|
+
{ id: 'resources', setup: { defaultRoleFeatures: { admin: ['resources.*'] } } },
|
|
29
|
+
{ id: 'staff', setup: { defaultRoleFeatures: { admin: ['staff.*', 'staff.leave_requests.manage'], employee: ['staff.leave_requests.send', 'staff.my_availability.view', 'staff.my_availability.manage', 'staff.my_leave_requests.view', 'staff.my_leave_requests.send'] } } },
|
|
30
|
+
{ id: 'example', setup: { defaultRoleFeatures: { admin: ['example.*'], employee: ['example.*', 'example.widgets.*'] } } },
|
|
31
|
+
]
|
|
32
|
+
registerModules(testModules)
|
|
33
|
+
|
|
4
34
|
// Mock DI container and EM
|
|
5
35
|
const persistAndFlush = jest.fn()
|
|
6
36
|
const findOne = jest.fn()
|