@things-factory/code-base 10.1.79 → 10.1.105

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/code-base",
3
- "version": "10.1.79",
3
+ "version": "10.1.105",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -31,5 +31,5 @@
31
31
  "@things-factory/auth-base": "^10.1.79",
32
32
  "@things-factory/shell": "^10.1.79"
33
33
  },
34
- "gitHead": "94cde9ba17dfdddb0a16139b71e5a7e370977e4a"
34
+ "gitHead": "c8ad9b40d57709beefd5e3d6167fd9cab6efea3a"
35
35
  }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * A common code and its details are looked up inside the caller's domain.
3
+ *
4
+ * The lookups asked by id alone: a detail could be put under another domain's code group, a group could take
5
+ * over another domain's details, and the grid save (`updateMultiple…`, cuFlag 'M') overwrote a row of another
6
+ * domain by its id. This runs the real mutations on sqlite and reads the rows back.
7
+ */
8
+ import 'reflect-metadata'
9
+
10
+ const { existsSync } = require('fs')
11
+ const { join } = require('path')
12
+ const { DataSource } = require('typeorm')
13
+
14
+ const shell = require('@things-factory/shell')
15
+ const authBase = require('@things-factory/auth-base')
16
+
17
+ function loadCompiled(modulePath: string): any {
18
+ const resolved = join(__dirname, '..', 'dist-server', `${modulePath}.js`)
19
+ if (!existsSync(resolved)) {
20
+ throw new Error(`code-base build output is missing: ${resolved}\nBuild it first: npm run build:server`)
21
+ }
22
+ return require(resolved)
23
+ }
24
+
25
+ let dataSource: any
26
+ let CommonCode: any
27
+ let CommonCodeDetail: any
28
+ let CommonCodeMutation: any
29
+ let CommonCodeDetailMutation: any
30
+
31
+ let domain: any
32
+ let other: any
33
+ let user: any
34
+ let ourCode: any
35
+ let theirCode: any
36
+ let ourDetail: any
37
+ let theirDetail: any
38
+
39
+ beforeAll(async () => {
40
+ const codeBase = loadCompiled('service/index')
41
+ CommonCode = codeBase.CommonCode
42
+ CommonCodeDetail = codeBase.CommonCodeDetail
43
+ CommonCodeMutation = loadCompiled('service/common-code/common-code-mutation').CommonCodeMutation
44
+ CommonCodeDetailMutation = loadCompiled('service/common-code-detail/common-code-detail-mutation').CommonCodeDetailMutation
45
+
46
+ dataSource = new DataSource({
47
+ type: 'sqlite',
48
+ database: ':memory:',
49
+ synchronize: true,
50
+ dropSchema: true,
51
+ namingStrategy: new shell.NamingStrategy(),
52
+ entities: [...shell.entities, ...authBase.entities, ...codeBase.entities],
53
+ logging: false
54
+ })
55
+ await dataSource.initialize()
56
+ shell.addDataSource('default', dataSource)
57
+ shell.addDataSource('tx', dataSource)
58
+ })
59
+
60
+ beforeEach(async () => {
61
+ await dataSource.synchronize(true)
62
+ const domains = dataSource.getRepository(shell.Domain)
63
+ domain = await domains.save({ name: 'ours', subdomain: 'ours' })
64
+ other = await domains.save({ name: 'theirs', subdomain: 'theirs' })
65
+ user = await dataSource.getRepository(authBase.User).save({ name: 'tester', email: 'tester@code-base.test', locale: 'en' })
66
+
67
+ const codes = dataSource.getRepository(CommonCode)
68
+ ourCode = await codes.save({ domain, name: 'UNIT', description: 'ours' })
69
+ theirCode = await codes.save({ domain: other, name: 'UNIT', description: 'theirs' })
70
+
71
+ const details = dataSource.getRepository(CommonCodeDetail)
72
+ ourDetail = await details.save({ domain, commonCode: ourCode, name: 'EA', description: 'ours', rank: 1 })
73
+ theirDetail = await details.save({ domain: other, commonCode: theirCode, name: 'EA', description: 'theirs', rank: 1 })
74
+ })
75
+
76
+ afterAll(async () => {
77
+ if (!dataSource) return
78
+ shell.removeDataSource('default')
79
+ shell.removeDataSource('tx')
80
+ await dataSource.destroy()
81
+ })
82
+
83
+ const context = () => ({ state: { domain, user, tx: dataSource.manager } })
84
+
85
+ async function groupOf(detailId: string) {
86
+ return (await dataSource.getRepository(CommonCodeDetail).findOneBy({ id: detailId }))?.commonCodeId ?? null
87
+ }
88
+
89
+ describe("a detail's code group comes from the caller's domain", () => {
90
+ it('a group of this domain is accepted', async () => {
91
+ const created = await new CommonCodeDetailMutation().createCommonCodeDetail(
92
+ { name: 'BOX', description: 'box', rank: 2, commonCode: { id: ourCode.id } },
93
+ context()
94
+ )
95
+ expect(await groupOf(created.id)).toBe(ourCode.id)
96
+ })
97
+
98
+ it('createCommonCodeDetail refuses a group of another domain', async () => {
99
+ await expect(
100
+ new CommonCodeDetailMutation().createCommonCodeDetail(
101
+ { name: 'BOX', description: 'box', rank: 2, commonCode: { id: theirCode.id } },
102
+ context()
103
+ )
104
+ ).rejects.toThrow(/common code not found in this domain/)
105
+ expect(await dataSource.getRepository(CommonCodeDetail).countBy({ name: 'BOX' })).toBe(0)
106
+ })
107
+
108
+ it('updateCommonCodeDetail refuses a group of another domain', async () => {
109
+ await expect(
110
+ new CommonCodeDetailMutation().updateCommonCodeDetail(ourDetail.id, { commonCode: { id: theirCode.id } }, context())
111
+ ).rejects.toThrow(/common code not found in this domain/)
112
+ expect(await groupOf(ourDetail.id)).toBe(ourCode.id)
113
+ })
114
+
115
+ it('updateMultipleCommonCodeDetail refuses it on a new row and on an edited one', async () => {
116
+ const mutation = new CommonCodeDetailMutation()
117
+ await expect(
118
+ mutation.updateMultipleCommonCodeDetail(
119
+ [{ cuFlag: '+', name: 'BOX', rank: 2, labels: {}, commonCode: { id: theirCode.id } }],
120
+ context()
121
+ )
122
+ ).rejects.toThrow(/common code not found in this domain/)
123
+ await expect(
124
+ mutation.updateMultipleCommonCodeDetail(
125
+ [{ cuFlag: 'M', id: ourDetail.id, labels: {}, commonCode: { id: theirCode.id } }],
126
+ context()
127
+ )
128
+ ).rejects.toThrow(/common code not found in this domain/)
129
+ expect(await groupOf(ourDetail.id)).toBe(ourCode.id)
130
+ })
131
+
132
+ it("updateMultipleCommonCodeDetail does not edit another domain's detail by its id", async () => {
133
+ await expect(
134
+ new CommonCodeDetailMutation().updateMultipleCommonCodeDetail(
135
+ [{ cuFlag: 'M', id: theirDetail.id, labels: {}, description: 'overwritten' }],
136
+ context()
137
+ )
138
+ ).rejects.toThrow(/common code detail not found in this domain/)
139
+ expect((await dataSource.getRepository(CommonCodeDetail).findOneBy({ id: theirDetail.id })).description).toBe('theirs')
140
+ })
141
+ })
142
+
143
+ describe("a code group's details and rows come from the caller's domain", () => {
144
+ it('createCommonCode refuses a detail of another domain', async () => {
145
+ await expect(
146
+ new CommonCodeMutation().createCommonCode({ name: 'COLOR', description: 'color', details: [theirDetail.id] }, context())
147
+ ).rejects.toThrow(/common code detail not found in this domain/)
148
+ expect(await groupOf(theirDetail.id)).toBe(theirCode.id)
149
+ })
150
+
151
+ it('updateCommonCode refuses a detail of another domain', async () => {
152
+ await expect(
153
+ new CommonCodeMutation().updateCommonCode('UNIT', { details: [theirDetail.id] }, context())
154
+ ).rejects.toThrow(/common code detail not found in this domain/)
155
+ expect(await groupOf(theirDetail.id)).toBe(theirCode.id)
156
+ })
157
+
158
+ it('updateCommonCode keeps the details of this domain', async () => {
159
+ const updated = await new CommonCodeMutation().updateCommonCode('UNIT', { details: [ourDetail.id] }, context())
160
+ expect(updated.details.map((detail: any) => detail.id)).toEqual([ourDetail.id])
161
+ })
162
+
163
+ it("updateMultipleCommonCode does not edit another domain's code by its id", async () => {
164
+ await expect(
165
+ new CommonCodeMutation().updateMultipleCommonCode([{ cuFlag: 'M', id: theirCode.id, description: 'overwritten' }], context())
166
+ ).rejects.toThrow(/common code not found in this domain/)
167
+ expect((await dataSource.getRepository(CommonCode).findOneBy({ id: theirCode.id })).description).toBe('theirs')
168
+ })
169
+ })
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Every mutation of code-base asks for `common-code:mutation`, and the domain owner may call it.
3
+ *
4
+ * Until 2026-10-01 none declared anything: any member of a domain could create, rename or delete its
5
+ * common codes and their details. The queries are not gated yet and are not measured here.
6
+ */
7
+ import { gatesOf, openFields } from '../../../test/helpers/open-doors'
8
+
9
+ let gates: Awaited<ReturnType<typeof gatesOf>>
10
+
11
+ beforeAll(async () => {
12
+ gates = await gatesOf('code-base')
13
+ })
14
+
15
+ it('no mutation is open', () => {
16
+ expect(openFields(gates.Mutation)).toEqual([])
17
+ })
18
+
19
+ it('every mutation asks for common-code:mutation and admits the domain owner', () => {
20
+ for (const [field, gate] of Object.entries(gates.Mutation)) {
21
+ expect({ field, ...gate }).toEqual({ field, category: 'common-code', privilege: 'mutation', owner: true, super: false })
22
+ }
23
+ })
24
+
25
+ it('the walk reaches every mutation', () => {
26
+ expect(Object.keys(gates.Mutation)).toHaveLength(12)
27
+ })