@owlmeans/test-auth 0.1.18-rc.3 → 0.1.18-rc.31
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/README.md +1 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/testing-auth-unit/SKILL.md +44 -13
- package/build/trusted.d.ts +4 -0
- package/build/trusted.d.ts.map +1 -1
- package/build/trusted.js +27 -10
- package/build/trusted.js.map +1 -1
- package/package.json +10 -10
- package/src/trusted.ts +38 -15
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
20
20
|
your project's skill store (`.agents/skills/`):
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
npx @owlmeans/agent-skills
|
|
23
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.32
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/test-auth",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18-rc.31",
|
|
5
|
+
"generatedAt": "2026-09-22T12:36:21.786Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: testing-auth-unit
|
|
3
|
-
description: Category-B unit tests for OwlMeans Common — auth/authz packages where the test substitutes a fake authenticated identity via @owlmeans/test-auth. Auto-invoked when writing tests in auth, auth-common, basic-keys, basic-envelope, did, client-auth, client-did, server-auth,
|
|
3
|
+
description: Category-B unit tests for OwlMeans Common — auth/authz packages where the test substitutes a fake authenticated identity via @owlmeans/test-auth. Auto-invoked when writing tests in auth, auth-common, auth-otp, basic-keys, basic-envelope, did, client-auth, client-did, client-iam, client-payment, mui-oidc-rp, oidc, server-auth, server-auth-otp, server-oidc-*, web-auth, web-oidc-*, wled.
|
|
4
4
|
---
|
|
5
5
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
6
6
|
|
|
7
7
|
# Auth Unit Tests — Category B
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Install:** `"@owlmeans/test-auth": "^0.1.18-rc.31"` in `devDependencies`
|
|
10
|
+
|
|
11
|
+
Category B applies to the auth/authz packages: `auth`, `auth-common`, `auth-otp`, `basic-envelope`, `basic-keys`, `client-auth`, `client-did`, `client-iam`, `client-payment`, `did`, `mui-oidc-rp`, `oidc`, `server-auth`, `server-auth-otp`, `server-oidc-provider`, `server-oidc-rp`, `web-auth`, `web-oidc-provider`, `web-oidc-rp`, `wled`. These tests run under `bun test` (no Playwright even when there are React bits) and are the **only** place mocks are allowed — and only for authentication/authorization, via `@owlmeans/test-auth`.
|
|
10
12
|
|
|
11
13
|
For background on the protocol you're mocking, read the `auth-protocol` skill.
|
|
12
14
|
|
|
@@ -14,23 +16,37 @@ For background on the protocol you're mocking, read the `auth-protocol` skill.
|
|
|
14
16
|
|
|
15
17
|
| Helper | Purpose |
|
|
16
18
|
|---|---|
|
|
17
|
-
| `makeFixtureKeyPair(seed?)` | Deterministic Ed25519 `KeyPairModel`. Same seed → same keys. |
|
|
18
|
-
| `makeMemoryTrustedResource(records?)` | `Resource<TrustedRecord>` that satisfies `trust()` lookups. |
|
|
19
|
-
| `makeMockGuard(
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
19
|
+
| `makeFixtureKeyPair(seed?)` | Deterministic Ed25519 `KeyPairModel`. Same seed → same keys; no seed → a fresh random pair. |
|
|
20
|
+
| `makeMemoryTrustedResource(records?, alias?)` | `Resource<TrustedRecord>` that satisfies `trust()` lookups. `alias` defaults to `TRUSTED`. |
|
|
21
|
+
| `makeMockGuard(opts?)` | `GuardService` resolving to a chosen `Auth`, registerable exactly like a real guard. |
|
|
22
|
+
| `MockGuardOptions` | `{ alias?, auth?, allow?, token? }` — see below. |
|
|
23
|
+
| `withAuth(ctx, auth, alias?)` | Registers a mock guard on `ctx` that resolves to `auth`, and returns `ctx`. |
|
|
24
|
+
| `signMockEnvelope(msg, type, kind?, kp?)` | Wraps `makeEnvelopeModel` with a fixture keypair and signs it; `kind` defaults to `EnvelopeKind.Token`. |
|
|
25
|
+
| `makeBearer(auth, kp?)` | Produces an `ED25519-BASIC-TOKEN <encoded>` header value from an `Auth`. |
|
|
26
|
+
| `SUPERUSER`, `USER`, `SERVICE` | Canonical `Auth` payload fixtures (`Superuser` / `User` / `Service` roles, `scopes: ['*']`). |
|
|
27
|
+
|
|
28
|
+
`MockGuardOptions` carries the whole seam: `auth` is what `handle` resolves into the response,
|
|
29
|
+
`allow` is the predicate `match` reports (default: always match), `token` is what the client-side
|
|
30
|
+
`authenticated()` answers, and `alias` defaults to `DEFAULT_GUARD` so the mock takes the place a
|
|
31
|
+
real guard would occupy. `withAuth` is the shorthand for the common case.
|
|
32
|
+
|
|
33
|
+
**The last registration for an alias wins.** `registerService` in `@owlmeans/context` assigns
|
|
34
|
+
`services[service.alias]`, so it overwrites silently — there is no "first one keeps the slot".
|
|
35
|
+
Register the mock **after** anything that appends a real guard under the same alias (an
|
|
36
|
+
`append*` mixin the package documents, or a context factory that wires one), or give the mock
|
|
37
|
+
its own alias and point the entrypoint's guard at that. A mock registered first and then
|
|
38
|
+
overwritten leaves the spec exercising real cryptography and failing on a trusted-record
|
|
39
|
+
lookup, which reads as an unrelated failure.
|
|
24
40
|
|
|
25
41
|
## `tests/context.ts` pattern
|
|
26
42
|
|
|
27
43
|
```ts
|
|
28
|
-
import { AppType,
|
|
44
|
+
import { AppType, makeBasicContext } from '@owlmeans/context'
|
|
29
45
|
import { makeMemoryTrustedResource, withAuth, USER } from '@owlmeans/test-auth'
|
|
30
46
|
|
|
31
47
|
export const makeTestCtx = () => {
|
|
32
48
|
const ctx = makeBasicContext({
|
|
33
|
-
ready: false, service: '<pkg>-tests',
|
|
49
|
+
ready: false, service: '<pkg>-tests', type: AppType.Backend, services: {},
|
|
34
50
|
})
|
|
35
51
|
ctx.registerResource(makeMemoryTrustedResource([
|
|
36
52
|
{ id: 'svc', name: 'svc', credential: '<fixture-pubkey>' },
|
|
@@ -40,6 +56,21 @@ export const makeTestCtx = () => {
|
|
|
40
56
|
}
|
|
41
57
|
```
|
|
42
58
|
|
|
59
|
+
One factory, one context: the trusted resource and the mock guard are registered inside it, in the
|
|
60
|
+
order a real app's `makeContext` would.
|
|
61
|
+
|
|
62
|
+
`makeMemoryTrustedResource` implements only `load`, `save` and `create` — enough for the `trust()`
|
|
63
|
+
boundary and for seeding from a spec; every other resource method throws. A read takes an id or a
|
|
64
|
+
single-field criteria over `id` or `name`, and anything wider is refused with
|
|
65
|
+
`UnsupportedArgumentError` rather than scanned: a query the mock cannot answer exactly is a test
|
|
66
|
+
reaching past its seam, and the honest fix is integration coverage.
|
|
67
|
+
|
|
68
|
+
Every seeded record needs an `id`; a record without one is rejected on construction, and so is a
|
|
69
|
+
`create` for an id already held. `trust(context, resource, userName)` looks up by `name` by
|
|
70
|
+
default, so seed both fields. Give the record a `credential` (a public key) when the code under
|
|
71
|
+
test only verifies, and a `secret` (a private key) when it must sign as the trusted party —
|
|
72
|
+
`trust()` prefers `secret` and falls back to `credential`.
|
|
73
|
+
|
|
43
74
|
The trusted resource, mock guard, and any fixture keys are the **only** mocks allowed. Everything else (the service under test, sibling packages it depends on) is the real thing.
|
|
44
75
|
|
|
45
76
|
## Spec shape
|
|
@@ -65,10 +96,10 @@ describe('@owlmeans/basic-keys — packAuthCredentials', () => {
|
|
|
65
96
|
- **Mocks only for auth/authz.** No mocks for databases, network, or any sibling package. If you need one, move the spec to category C and write an integration test.
|
|
66
97
|
- **Don't replicate `@owlmeans/test-auth` helpers in your package.** If you need a new mocking primitive (e.g. an OIDC userinfo endpoint stub), add it to `@owlmeans/test-auth` and its skill — never to a per-package `tests/`.
|
|
67
98
|
- **Use deterministic seeds.** `makeFixtureKeyPair('<seed>')` keeps signatures stable run-to-run; random keypairs break snapshot-style assertions.
|
|
68
|
-
- **Cover SKILL.md cases first.** The package's `.agents/skills
|
|
99
|
+
- **Cover SKILL.md cases first.** The package's own skill (its directory under `.agents/skills/` — named for the skill, which is not always the package name) documents the consumer-facing surface, and that is the test priority order.
|
|
69
100
|
- **Max 3-4 tests per method/function.** Same as category A.
|
|
70
101
|
- **Don't test types or utils.**
|
|
71
102
|
|
|
72
103
|
## When you actually want OIDC end-to-end
|
|
73
104
|
|
|
74
|
-
|
|
105
|
+
`@owlmeans/test-auth` ships no fake JWKS server. Use the real `makeOidcGuard` against the in-memory trusted resource and a fixture keypair. If a future test needs a hostable IdP fake, add it to `@owlmeans/test-auth` and document it here — don't bolt it on per package.
|
package/build/trusted.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ import type { Resource } from '@owlmeans/resource';
|
|
|
9
9
|
* `trust()` boundary plus straightforward seeding from tests. Other
|
|
10
10
|
* operations throw, so tests that need them have to be honest about
|
|
11
11
|
* adding integration coverage instead of leaning on the mock.
|
|
12
|
+
*
|
|
13
|
+
* A read takes an id or a single-field criteria over `id` or `name`. Anything
|
|
14
|
+
* wider is refused rather than scanned: the mock holds a handful of fixtures
|
|
15
|
+
* and a query it cannot answer exactly is a test reaching past its seam.
|
|
12
16
|
*/
|
|
13
17
|
export declare const makeMemoryTrustedResource: (records?: TrustedRecord[], alias?: string) => Resource<TrustedRecord>;
|
|
14
18
|
//# sourceMappingURL=trusted.d.ts.map
|
package/build/trusted.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trusted.d.ts","sourceRoot":"","sources":["../src/trusted.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"trusted.d.ts","sourceRoot":"","sources":["../src/trusted.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,KAAK,EAA0B,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAe1E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,aAC3B,aAAa,EAAE,UACjB,MAAM,KACZ,QAAQ,CAAC,aAAa,CAqExB,CAAA"}
|
package/build/trusted.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { appendContextual } from '@owlmeans/context';
|
|
2
|
+
import { UnsupportedArgumentError } from '@owlmeans/resource';
|
|
2
3
|
const TRUSTED_DEFAULT_ALIAS = 'TRUSTED';
|
|
4
|
+
/** The fields a `trust()` lookup keys on — the only index this map has. */
|
|
5
|
+
const SUPPORTED_FIELDS = ['id', 'name'];
|
|
3
6
|
const notImplemented = (op) => {
|
|
4
7
|
throw new Error(`@owlmeans/test-auth: in-memory TRUSTED resource does not implement ${op}()`);
|
|
5
8
|
};
|
|
@@ -13,6 +16,10 @@ const getter = (record, field) => record[field];
|
|
|
13
16
|
* `trust()` boundary plus straightforward seeding from tests. Other
|
|
14
17
|
* operations throw, so tests that need them have to be honest about
|
|
15
18
|
* adding integration coverage instead of leaning on the mock.
|
|
19
|
+
*
|
|
20
|
+
* A read takes an id or a single-field criteria over `id` or `name`. Anything
|
|
21
|
+
* wider is refused rather than scanned: the mock holds a handful of fixtures
|
|
22
|
+
* and a query it cannot answer exactly is a test reaching past its seam.
|
|
16
23
|
*/
|
|
17
24
|
export const makeMemoryTrustedResource = (records = [], alias = TRUSTED_DEFAULT_ALIAS) => {
|
|
18
25
|
const store = new Map();
|
|
@@ -29,16 +36,24 @@ export const makeMemoryTrustedResource = (records = [], alias = TRUSTED_DEFAULT_
|
|
|
29
36
|
}
|
|
30
37
|
return null;
|
|
31
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* @throws {UnsupportedArgumentError} on criteria naming anything but one supported field.
|
|
41
|
+
*/
|
|
42
|
+
const first = (idOrWhere) => {
|
|
43
|
+
if (typeof idOrWhere === 'string') {
|
|
44
|
+
return store.get(idOrWhere) ?? null;
|
|
45
|
+
}
|
|
46
|
+
const entries = Object.entries(idOrWhere).filter(([, value]) => value !== undefined);
|
|
47
|
+
const [entry, ...rest] = entries;
|
|
48
|
+
if (entry == null || rest.length > 0
|
|
49
|
+
|| !SUPPORTED_FIELDS.includes(entry[0]) || typeof entry[1] !== 'string') {
|
|
50
|
+
throw new UnsupportedArgumentError('test-auth:trusted:where');
|
|
51
|
+
}
|
|
52
|
+
const [field, value] = entry;
|
|
53
|
+
return field === 'id' ? store.get(value) ?? null : findByField(value, field);
|
|
54
|
+
};
|
|
32
55
|
const resource = {
|
|
33
|
-
load: async (
|
|
34
|
-
const fieldName = typeof field === 'string'
|
|
35
|
-
? field
|
|
36
|
-
: field?.field;
|
|
37
|
-
if (fieldName != null && fieldName !== 'id') {
|
|
38
|
-
return findByField(id, fieldName) ?? null;
|
|
39
|
-
}
|
|
40
|
-
return store.get(id) ?? null;
|
|
41
|
-
},
|
|
56
|
+
load: async (idOrWhere, _opts) => first(idOrWhere),
|
|
42
57
|
save: async (record) => {
|
|
43
58
|
if (record.id == null) {
|
|
44
59
|
throw new Error('@owlmeans/test-auth: cannot save TRUSTED record without id');
|
|
@@ -58,9 +73,11 @@ export const makeMemoryTrustedResource = (records = [], alias = TRUSTED_DEFAULT_
|
|
|
58
73
|
},
|
|
59
74
|
get: () => notImplemented('get'),
|
|
60
75
|
list: () => notImplemented('list'),
|
|
76
|
+
count: () => notImplemented('count'),
|
|
61
77
|
update: () => notImplemented('update'),
|
|
62
78
|
delete: () => notImplemented('delete'),
|
|
63
|
-
|
|
79
|
+
take: () => notImplemented('take'),
|
|
80
|
+
purge: () => notImplemented('purge'),
|
|
64
81
|
};
|
|
65
82
|
return appendContextual(alias, resource);
|
|
66
83
|
};
|
package/build/trusted.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trusted.js","sourceRoot":"","sources":["../src/trusted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"trusted.js","sourceRoot":"","sources":["../src/trusted.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/test-auth",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.31",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -21,18 +21,18 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
25
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/basic-envelope": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/basic-keys": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/resource": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/test": "^0.1.18-rc.
|
|
24
|
+
"@owlmeans/auth": "^0.1.18-rc.30",
|
|
25
|
+
"@owlmeans/auth-common": "^0.1.18-rc.32",
|
|
26
|
+
"@owlmeans/basic-envelope": "^0.1.18-rc.31",
|
|
27
|
+
"@owlmeans/basic-keys": "^0.1.18-rc.33",
|
|
28
|
+
"@owlmeans/context": "^0.1.18-rc.28",
|
|
29
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.31",
|
|
30
|
+
"@owlmeans/resource": "^0.1.18-rc.30",
|
|
31
|
+
"@owlmeans/test": "^0.1.18-rc.28"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@owlmeans/dep-config": "workspace:*",
|
|
35
|
-
"@types/bun": "^1.
|
|
35
|
+
"@types/bun": "^1.4.0",
|
|
36
36
|
"nodemon": "^3.1.14",
|
|
37
37
|
"typescript": "^7.0.2"
|
|
38
38
|
},
|
package/src/trusted.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { appendContextual } from '@owlmeans/context'
|
|
2
2
|
import type { TrustedRecord } from '@owlmeans/auth-common'
|
|
3
|
-
import type { Resource } from '@owlmeans/resource'
|
|
3
|
+
import type { Criteria, FirstOptions, Resource } from '@owlmeans/resource'
|
|
4
|
+
import { UnsupportedArgumentError } from '@owlmeans/resource'
|
|
4
5
|
|
|
5
6
|
const TRUSTED_DEFAULT_ALIAS = 'TRUSTED'
|
|
6
7
|
|
|
8
|
+
/** The fields a `trust()` lookup keys on — the only index this map has. */
|
|
9
|
+
const SUPPORTED_FIELDS = ['id', 'name']
|
|
10
|
+
|
|
7
11
|
const notImplemented = (op: string): never => {
|
|
8
12
|
throw new Error(`@owlmeans/test-auth: in-memory TRUSTED resource does not implement ${op}()`)
|
|
9
13
|
}
|
|
@@ -20,6 +24,10 @@ const getter = (record: TrustedRecord, field: string): unknown =>
|
|
|
20
24
|
* `trust()` boundary plus straightforward seeding from tests. Other
|
|
21
25
|
* operations throw, so tests that need them have to be honest about
|
|
22
26
|
* adding integration coverage instead of leaning on the mock.
|
|
27
|
+
*
|
|
28
|
+
* A read takes an id or a single-field criteria over `id` or `name`. Anything
|
|
29
|
+
* wider is refused rather than scanned: the mock holds a handful of fixtures
|
|
30
|
+
* and a query it cannot answer exactly is a test reaching past its seam.
|
|
23
31
|
*/
|
|
24
32
|
export const makeMemoryTrustedResource = (
|
|
25
33
|
records: TrustedRecord[] = [],
|
|
@@ -40,26 +48,39 @@ export const makeMemoryTrustedResource = (
|
|
|
40
48
|
return null
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @throws {UnsupportedArgumentError} on criteria naming anything but one supported field.
|
|
53
|
+
*/
|
|
54
|
+
const first = (idOrWhere: string | Criteria<TrustedRecord>): TrustedRecord | null => {
|
|
55
|
+
if (typeof idOrWhere === 'string') {
|
|
56
|
+
return store.get(idOrWhere) ?? null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const entries = Object.entries(idOrWhere).filter(([, value]) => value !== undefined)
|
|
60
|
+
const [entry, ...rest] = entries
|
|
61
|
+
if (entry == null || rest.length > 0
|
|
62
|
+
|| !SUPPORTED_FIELDS.includes(entry[0]) || typeof entry[1] !== 'string') {
|
|
63
|
+
throw new UnsupportedArgumentError('test-auth:trusted:where')
|
|
64
|
+
}
|
|
65
|
+
const [field, value] = entry as [string, string]
|
|
66
|
+
|
|
67
|
+
return field === 'id' ? store.get(value) ?? null : findByField(value, field)
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
const resource: Partial<Resource<TrustedRecord>> = {
|
|
44
|
-
load: async
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
: (field as { field?: string } | undefined)?.field
|
|
48
|
-
if (fieldName != null && fieldName !== 'id') {
|
|
49
|
-
return (findByField(id, fieldName) as Type | null) ?? null
|
|
50
|
-
}
|
|
51
|
-
return (store.get(id) as Type | undefined) ?? null
|
|
52
|
-
},
|
|
71
|
+
load: async (
|
|
72
|
+
idOrWhere: string | Criteria<TrustedRecord>, _opts?: FirstOptions<TrustedRecord>
|
|
73
|
+
): Promise<TrustedRecord | null> => first(idOrWhere),
|
|
53
74
|
|
|
54
|
-
save: async
|
|
75
|
+
save: async (record: Partial<TrustedRecord>) => {
|
|
55
76
|
if (record.id == null) {
|
|
56
77
|
throw new Error('@owlmeans/test-auth: cannot save TRUSTED record without id')
|
|
57
78
|
}
|
|
58
79
|
store.set(record.id, record as TrustedRecord)
|
|
59
|
-
return record as
|
|
80
|
+
return record as TrustedRecord
|
|
60
81
|
},
|
|
61
82
|
|
|
62
|
-
create: async
|
|
83
|
+
create: async (record: Partial<TrustedRecord>) => {
|
|
63
84
|
if (record.id == null) {
|
|
64
85
|
throw new Error('@owlmeans/test-auth: cannot create TRUSTED record without id')
|
|
65
86
|
}
|
|
@@ -67,14 +88,16 @@ export const makeMemoryTrustedResource = (
|
|
|
67
88
|
throw new Error(`@owlmeans/test-auth: TRUSTED record ${record.id} already exists`)
|
|
68
89
|
}
|
|
69
90
|
store.set(record.id, record as TrustedRecord)
|
|
70
|
-
return record as
|
|
91
|
+
return record as TrustedRecord
|
|
71
92
|
},
|
|
72
93
|
|
|
73
94
|
get: () => notImplemented('get'),
|
|
74
95
|
list: () => notImplemented('list'),
|
|
96
|
+
count: () => notImplemented('count'),
|
|
75
97
|
update: () => notImplemented('update'),
|
|
76
98
|
delete: () => notImplemented('delete'),
|
|
77
|
-
|
|
99
|
+
take: () => notImplemented('take'),
|
|
100
|
+
purge: () => notImplemented('purge'),
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
return appendContextual<Resource<TrustedRecord>>(alias, resource)
|