@things-factory/auth-base 10.1.16 → 10.1.17

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/auth-base",
3
- "version": "10.1.16",
3
+ "version": "10.1.17",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -48,5 +48,5 @@
48
48
  "passport-jwt": "^4.0.0",
49
49
  "passport-local": "^1.0.0"
50
50
  },
51
- "gitHead": "aff86bc162b42c1d933711f7bc85a073f1ddcd29"
51
+ "gitHead": "0c8214182702162ceb5398bdcf42ef14ea672394"
52
52
  }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * App binding lookups — against a real database.
3
+ *
4
+ * Both defects here are invisible to a pure test. One is about which tenant's rows come
5
+ * back; the other is about the order paging and filtering happen in, which only shows up
6
+ * once there are more rows than fit on a page. Neither can be seen by reading the function.
7
+ */
8
+
9
+ import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
10
+ import { loadCompiled } from './compiled'
11
+
12
+ const { User } = loadCompiled('service/user/user')
13
+ const { AppBindingQuery } = loadCompiled('service/app-binding/app-binding-query')
14
+
15
+ const query = new AppBindingQuery()
16
+
17
+ let ours: any
18
+ let theirs: any
19
+
20
+ function context(domain: any) {
21
+ return { state: { domain } } as any
22
+ }
23
+
24
+ async function makeUser(domain: any, username: string, userType: string) {
25
+ return await getRepository(User).save({
26
+ username,
27
+ email: `${username}@example.com`,
28
+ name: username,
29
+ userType,
30
+ salt: User.generateSalt(),
31
+ status: 'activated',
32
+ domains: [domain]
33
+ })
34
+ }
35
+
36
+ beforeAll(async () => {
37
+ await openAuthDatabase()
38
+ })
39
+
40
+ afterAll(async () => {
41
+ await closeAuthDatabase()
42
+ })
43
+
44
+ beforeEach(async () => {
45
+ await resetAuthDatabase()
46
+
47
+ const a = await getRepository(Domain).save({ name: 'Plant A', subdomain: 'plant-a' })
48
+ const b = await getRepository(Domain).save({ name: 'Plant B', subdomain: 'plant-b' })
49
+
50
+ ours = await getRepository(Domain).findOneBy({ id: a.id })
51
+ theirs = await getRepository(Domain).findOneBy({ id: b.id })
52
+ })
53
+
54
+ describe('one binding', () => {
55
+ it('answers for a binding in this domain', async () => {
56
+ const mine = await makeUser(ours, 'app-mine', 'application')
57
+
58
+ const found = await query.appBinding(mine.id, context(ours))
59
+
60
+ expect(found?.id).toBe(mine.id)
61
+ })
62
+
63
+ it('does not answer for a binding in another domain', async () => {
64
+ /*
65
+ * ⚠ The isolation case. The resolver read `domain` out of the context and never used
66
+ * it — `findOneBy({ id, userType })` searched every tenant, and a comment beside it
67
+ * said "TODO should check domain is available". An id from anywhere returned that
68
+ * application user's name, email and reference.
69
+ */
70
+ const foreign = await makeUser(theirs, 'app-theirs', 'application')
71
+
72
+ const found = await query.appBinding(foreign.id, context(ours))
73
+
74
+ expect(found).toBeFalsy()
75
+ })
76
+
77
+ it('answers the same for a foreign id as for one that does not exist', async () => {
78
+ /*
79
+ * Deliberate: a different answer would confirm that some id is real somewhere else.
80
+ */
81
+ const foreign = await makeUser(theirs, 'app-theirs', 'application')
82
+
83
+ const foreignAnswer = await query.appBinding(foreign.id, context(ours))
84
+ const nonsenseAnswer = await query.appBinding('00000000-0000-4000-8000-000000000000', context(ours))
85
+
86
+ expect(foreignAnswer).toEqual(nonsenseAnswer)
87
+ })
88
+
89
+ it('does not answer for a person, only for an application', async () => {
90
+ const person = await makeUser(ours, 'someone', 'user')
91
+
92
+ expect(await query.appBinding(person.id, context(ours))).toBeFalsy()
93
+ })
94
+ })
95
+
96
+ describe('the list', () => {
97
+ it('returns only the applications of this domain', async () => {
98
+ await makeUser(ours, 'app-one', 'application')
99
+ await makeUser(ours, 'a-person', 'user')
100
+ await makeUser(theirs, 'app-theirs', 'application')
101
+
102
+ const { items, total } = await query.appBindings({ filters: [] } as any, context(ours))
103
+
104
+ expect(items.map((one: any) => one.username)).toEqual(['app-one'])
105
+ expect(total).toBe(1)
106
+ })
107
+
108
+ it('counts every match, not the ones on this page', async () => {
109
+ /*
110
+ * ⚠ The paging case, and it needs rows to show at all.
111
+ *
112
+ * The filter used to run in JavaScript on whatever the paged query returned, and the
113
+ * total was `items.length` — the survivors of that one page. A grid asking for page 1
114
+ * of 2 was told the total was 2, so it never offered page 2 and the remaining bindings
115
+ * could not be reached from the screen at all.
116
+ */
117
+ for (let i = 0; i < 5; i++) {
118
+ await makeUser(ours, `app-${i}`, 'application')
119
+ }
120
+
121
+ const { items, total } = await query.appBindings(
122
+ { filters: [], pagination: { page: 1, limit: 2 } } as any,
123
+ context(ours)
124
+ )
125
+
126
+ expect(items).toHaveLength(2)
127
+ expect(total).toBe(5)
128
+ })
129
+
130
+ it('fills a page with matches rather than with whatever the page happened to hold', async () => {
131
+ /*
132
+ * The other half of the same defect. Interleaving people and applications used to leave
133
+ * a page of two carrying one row, or none, depending on the order rows came back.
134
+ */
135
+ for (let i = 0; i < 4; i++) {
136
+ await makeUser(ours, `person-${i}`, 'user')
137
+ await makeUser(ours, `app-${i}`, 'application')
138
+ }
139
+
140
+ const { items, total } = await query.appBindings(
141
+ { filters: [], pagination: { page: 1, limit: 3 } } as any,
142
+ context(ours)
143
+ )
144
+
145
+ expect(items).toHaveLength(3)
146
+ expect(items.every((one: any) => one.userType === 'application')).toBe(true)
147
+ expect(total).toBe(4)
148
+ })
149
+
150
+ it('reaches the last page', async () => {
151
+ for (let i = 0; i < 5; i++) {
152
+ await makeUser(ours, `app-${i}`, 'application')
153
+ }
154
+
155
+ const { items } = await query.appBindings(
156
+ { filters: [], pagination: { page: 3, limit: 2 } } as any,
157
+ context(ours)
158
+ )
159
+
160
+ expect(items).toHaveLength(1)
161
+ })
162
+ })