@synnaxlabs/client 0.30.0 → 0.31.0
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/.turbo/turbo-build.log +6 -6
- package/api/client.api.md +615 -261
- package/dist/access/client.d.ts +2 -7
- package/dist/access/client.d.ts.map +1 -1
- package/dist/access/payload.d.ts +7 -102
- package/dist/access/payload.d.ts.map +1 -1
- package/dist/access/policy/client.d.ts +17 -0
- package/dist/access/policy/client.d.ts.map +1 -0
- package/dist/access/policy/external.d.ts +3 -0
- package/dist/access/policy/external.d.ts.map +1 -0
- package/dist/access/policy/index.d.ts +2 -0
- package/dist/access/policy/index.d.ts.map +1 -0
- package/dist/access/policy/payload.d.ts +163 -0
- package/dist/access/policy/payload.d.ts.map +1 -0
- package/dist/access/policy/policy.spec.d.ts +2 -0
- package/dist/access/policy/policy.spec.d.ts.map +1 -0
- package/dist/access/policy/retriever.d.ts +36 -0
- package/dist/access/policy/retriever.d.ts.map +1 -0
- package/dist/access/policy/writer.d.ts +9 -0
- package/dist/access/policy/writer.d.ts.map +1 -0
- package/dist/auth/auth.d.ts +6 -30
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/channel/payload.d.ts +17 -17
- package/dist/channel/payload.d.ts.map +1 -1
- package/dist/channel/retriever.d.ts +8 -8
- package/dist/client.cjs +31 -21
- package/dist/client.js +2962 -2233
- package/dist/framer/client.d.ts +4 -1
- package/dist/framer/client.d.ts.map +1 -1
- package/dist/framer/frame.d.ts +27 -80
- package/dist/framer/frame.d.ts.map +1 -1
- package/dist/framer/streamer.d.ts +3 -1
- package/dist/framer/streamer.d.ts.map +1 -1
- package/dist/framer/writer.d.ts +24 -16
- package/dist/framer/writer.d.ts.map +1 -1
- package/dist/hardware/device/client.d.ts +2 -2
- package/dist/hardware/device/payload.d.ts +1 -1
- package/dist/hardware/device/payload.d.ts.map +1 -1
- package/dist/hardware/rack/payload.d.ts +1 -1
- package/dist/hardware/rack/payload.d.ts.map +1 -1
- package/dist/hardware/task/client.d.ts +2 -2
- package/dist/hardware/task/ni/types.d.ts +16 -16
- package/dist/hardware/task/payload.d.ts +13 -13
- package/dist/hardware/task/payload.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/label/payload.d.ts +1 -1
- package/dist/label/payload.d.ts.map +1 -1
- package/dist/label/writer.d.ts +5 -5
- package/dist/ontology/client.d.ts +32 -30
- package/dist/ontology/client.d.ts.map +1 -1
- package/dist/ontology/payload.d.ts +62 -63
- package/dist/ontology/payload.d.ts.map +1 -1
- package/dist/ranger/payload.d.ts +2 -2
- package/dist/ranger/payload.d.ts.map +1 -1
- package/dist/ranger/writer.d.ts +5 -5
- package/dist/user/client.d.ts +13 -3
- package/dist/user/client.d.ts.map +1 -1
- package/dist/user/payload.d.ts +34 -3
- package/dist/user/payload.d.ts.map +1 -1
- package/dist/user/retriever.d.ts +21 -0
- package/dist/user/retriever.d.ts.map +1 -0
- package/dist/user/user.spec.d.ts +2 -0
- package/dist/user/user.spec.d.ts.map +1 -0
- package/dist/user/writer.d.ts +11 -0
- package/dist/user/writer.d.ts.map +1 -0
- package/dist/workspace/lineplot/payload.d.ts +1 -1
- package/dist/workspace/lineplot/payload.d.ts.map +1 -1
- package/dist/workspace/payload.d.ts +1 -1
- package/dist/workspace/payload.d.ts.map +1 -1
- package/dist/workspace/schematic/client.d.ts.map +1 -1
- package/dist/workspace/schematic/payload.d.ts +1 -1
- package/dist/workspace/schematic/payload.d.ts.map +1 -1
- package/examples/node/package-lock.json +963 -134
- package/examples/node/package.json +1 -1
- package/package.json +3 -3
- package/src/access/client.ts +4 -70
- package/src/access/payload.ts +14 -24
- package/src/access/policy/client.ts +65 -0
- package/src/access/policy/external.ts +11 -0
- package/src/access/policy/index.ts +10 -0
- package/src/access/policy/payload.ts +45 -0
- package/src/access/policy/policy.spec.ts +331 -0
- package/src/access/policy/retriever.ts +43 -0
- package/src/access/policy/writer.ts +65 -0
- package/src/auth/auth.ts +32 -10
- package/src/channel/payload.ts +2 -2
- package/src/framer/client.ts +7 -1
- package/src/framer/frame.spec.ts +21 -12
- package/src/framer/frame.ts +9 -24
- package/src/framer/streamer.spec.ts +48 -0
- package/src/framer/streamer.ts +7 -4
- package/src/framer/writer.ts +0 -2
- package/src/hardware/device/payload.ts +2 -2
- package/src/hardware/rack/payload.ts +2 -2
- package/src/hardware/task/payload.ts +2 -2
- package/src/index.ts +16 -13
- package/src/label/payload.ts +2 -2
- package/src/ontology/client.ts +35 -34
- package/src/ontology/payload.ts +28 -35
- package/src/ranger/payload.ts +5 -7
- package/src/setupspecs.ts +2 -2
- package/src/user/client.ts +63 -19
- package/src/user/payload.ts +14 -7
- package/src/user/retriever.ts +41 -0
- package/src/user/user.spec.ts +289 -0
- package/src/user/writer.ts +91 -0
- package/src/workspace/lineplot/payload.ts +2 -2
- package/src/workspace/payload.ts +2 -2
- package/src/workspace/schematic/client.ts +1 -1
- package/src/workspace/schematic/payload.ts +2 -2
- package/src/workspace/workspace.spec.ts +1 -1
- package/dist/access/access.spec.d.ts +0 -2
- package/dist/access/access.spec.d.ts.map +0 -1
- package/src/access/access.spec.ts +0 -276
package/src/ontology/payload.ts
CHANGED
|
@@ -19,12 +19,14 @@ export type RelationshipDelete = change.Delete<Relationship, undefined>;
|
|
|
19
19
|
|
|
20
20
|
export const resourceTypeZ = z.union([
|
|
21
21
|
z.literal("label"),
|
|
22
|
+
z.literal("allow_all"),
|
|
22
23
|
z.literal("builtin"),
|
|
23
24
|
z.literal("cluster"),
|
|
24
25
|
z.literal("channel"),
|
|
25
26
|
z.literal("node"),
|
|
26
27
|
z.literal("group"),
|
|
27
28
|
z.literal("range"),
|
|
29
|
+
z.literal("framer"),
|
|
28
30
|
z.literal("range-alias"),
|
|
29
31
|
z.literal("user"),
|
|
30
32
|
z.literal("workspace"),
|
|
@@ -35,25 +37,22 @@ export const resourceTypeZ = z.union([
|
|
|
35
37
|
z.literal("task"),
|
|
36
38
|
z.literal("policy"),
|
|
37
39
|
]);
|
|
38
|
-
|
|
39
40
|
export type ResourceType = z.infer<typeof resourceTypeZ>;
|
|
40
41
|
|
|
41
|
-
export const
|
|
42
|
-
export const
|
|
43
|
-
export const
|
|
42
|
+
export const BUILTIN_TYPE: ResourceType = "builtin";
|
|
43
|
+
export const CLUSTER_TYPE: ResourceType = "cluster";
|
|
44
|
+
export const NODE_TYPE: ResourceType = "node";
|
|
44
45
|
|
|
45
46
|
export const idZ = z.object({ type: resourceTypeZ, key: z.string() });
|
|
46
|
-
|
|
47
47
|
export type IDPayload = z.infer<typeof idZ>;
|
|
48
48
|
|
|
49
49
|
export const stringIDZ = z.string().transform((v) => {
|
|
50
50
|
const [type, key] = v.split(":");
|
|
51
|
-
return { type: type
|
|
51
|
+
return { type: resourceTypeZ.parse(type), key: key ?? "" };
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
export const crudeIDZ = z.union([stringIDZ, idZ]);
|
|
55
|
-
|
|
56
|
-
export type CrudeID = { type: ResourceType; key: string } | string;
|
|
55
|
+
export type CrudeID = z.input<typeof crudeIDZ>;
|
|
57
56
|
|
|
58
57
|
export class ID {
|
|
59
58
|
type: ResourceType;
|
|
@@ -63,35 +62,40 @@ export class ID {
|
|
|
63
62
|
if (args instanceof ID) {
|
|
64
63
|
this.type = args.type;
|
|
65
64
|
this.key = args.key;
|
|
66
|
-
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (typeof args === "string") {
|
|
67
68
|
const [type, key] = args.split(":");
|
|
68
69
|
this.type = type as ResourceType;
|
|
69
|
-
this.key = key;
|
|
70
|
-
|
|
71
|
-
this.type = args.type;
|
|
72
|
-
this.key = args.key;
|
|
70
|
+
this.key = key ?? "";
|
|
71
|
+
return;
|
|
73
72
|
}
|
|
73
|
+
this.type = args.type;
|
|
74
|
+
this.key = args.key;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
toString(): string {
|
|
77
78
|
return `${this.type}:${this.key}`;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
isType(): boolean {
|
|
82
|
+
return this.key === "";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
matchesType(type: ResourceType): boolean {
|
|
86
|
+
return this.type === type && this.isType();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get payload(): IDPayload {
|
|
90
|
+
return { type: this.type, key: this.key };
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
static readonly z = z.union([
|
|
93
|
+
static readonly z = z.union([z.instanceof(ID), crudeIDZ.transform((v) => new ID(v))]);
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
export const Root = new ID({ type: "builtin", key: "root" });
|
|
91
97
|
|
|
92
|
-
export const schemaFieldZ = z.object({
|
|
93
|
-
type: z.number(),
|
|
94
|
-
});
|
|
98
|
+
export const schemaFieldZ = z.object({ type: z.number() });
|
|
95
99
|
|
|
96
100
|
export type SchemaField = z.infer<typeof schemaFieldZ>;
|
|
97
101
|
|
|
@@ -99,7 +103,6 @@ export const schemaZ = z.object({
|
|
|
99
103
|
type: resourceTypeZ,
|
|
100
104
|
fields: z.record(schemaFieldZ),
|
|
101
105
|
});
|
|
102
|
-
|
|
103
106
|
export type Schema = z.infer<typeof schemaZ>;
|
|
104
107
|
|
|
105
108
|
export const resourceSchemaZ = z
|
|
@@ -109,12 +112,7 @@ export const resourceSchemaZ = z
|
|
|
109
112
|
schema: schemaZ.optional().nullable(),
|
|
110
113
|
data: z.record(z.unknown()).optional().nullable(),
|
|
111
114
|
})
|
|
112
|
-
.transform((resource) => {
|
|
113
|
-
return {
|
|
114
|
-
key: resource.id.toString(),
|
|
115
|
-
...resource,
|
|
116
|
-
};
|
|
117
|
-
});
|
|
115
|
+
.transform((resource) => ({ key: resource.id.toString(), ...resource }));
|
|
118
116
|
|
|
119
117
|
export type Resource<T extends UnknownRecord = UnknownRecord> = Omit<
|
|
120
118
|
z.output<typeof resourceSchemaZ>,
|
|
@@ -123,12 +121,7 @@ export type Resource<T extends UnknownRecord = UnknownRecord> = Omit<
|
|
|
123
121
|
|
|
124
122
|
export type RelationshipDirection = "from" | "to";
|
|
125
123
|
|
|
126
|
-
export const relationshipSchemaZ = z.object({
|
|
127
|
-
from: ID.z,
|
|
128
|
-
type: z.string(),
|
|
129
|
-
to: ID.z,
|
|
130
|
-
});
|
|
131
|
-
|
|
124
|
+
export const relationshipSchemaZ = z.object({ from: ID.z, type: z.string(), to: ID.z });
|
|
132
125
|
export type Relationship = z.infer<typeof relationshipSchemaZ>;
|
|
133
126
|
|
|
134
127
|
export const parseRelationship = (str: string): Relationship => {
|
package/src/ranger/payload.ts
CHANGED
|
@@ -28,9 +28,7 @@ export const payloadZ = z.object({
|
|
|
28
28
|
});
|
|
29
29
|
export type Payload = z.infer<typeof payloadZ>;
|
|
30
30
|
|
|
31
|
-
export const newPayloadZ = payloadZ.extend({
|
|
32
|
-
key: z.string().uuid().optional(),
|
|
33
|
-
});
|
|
31
|
+
export const newPayloadZ = payloadZ.extend({ key: z.string().uuid().optional() });
|
|
34
32
|
export type NewPayload = z.input<typeof newPayloadZ>;
|
|
35
33
|
|
|
36
34
|
export type ParamAnalysisResult =
|
|
@@ -77,11 +75,11 @@ export const analyzeParams = (ranges: Params): ParamAnalysisResult => {
|
|
|
77
75
|
} as const as ParamAnalysisResult;
|
|
78
76
|
};
|
|
79
77
|
|
|
80
|
-
export const
|
|
81
|
-
export const
|
|
78
|
+
export const ONTOLOGY_TYPE: ontology.ResourceType = "range";
|
|
79
|
+
export const ALIAS_ONTOLOGY_TYPE: ontology.ResourceType = "range-alias";
|
|
82
80
|
|
|
83
81
|
export const rangeOntologyID = (key: Key): ontology.ID =>
|
|
84
|
-
new ontology.ID({ type:
|
|
82
|
+
new ontology.ID({ type: ONTOLOGY_TYPE, key: key });
|
|
85
83
|
|
|
86
84
|
export const rangeAliasOntologyID = (key: Key): ontology.ID =>
|
|
87
|
-
new ontology.ID({ type:
|
|
85
|
+
new ontology.ID({ type: ALIAS_ONTOLOGY_TYPE, key: key });
|
package/src/setupspecs.ts
CHANGED
|
@@ -11,8 +11,8 @@ import Synnax, { type SynnaxProps } from "@/client";
|
|
|
11
11
|
|
|
12
12
|
export const HOST = "localhost";
|
|
13
13
|
export const PORT = 9090;
|
|
14
|
-
const USERNAME = "synnax"
|
|
15
|
-
const PASSWORD = "seldon"
|
|
14
|
+
const USERNAME = "synnax";
|
|
15
|
+
const PASSWORD = "seldon";
|
|
16
16
|
|
|
17
17
|
export const newClient = (...props: SynnaxProps[]): Synnax => {
|
|
18
18
|
let _props = {};
|
package/src/user/client.ts
CHANGED
|
@@ -7,31 +7,75 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { toArray } from "@synnaxlabs/x";
|
|
11
12
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import { MultipleFoundError, NotFoundError } from "@/errors";
|
|
14
|
+
import { type Key, type NewUser, type User } from "@/user/payload";
|
|
15
|
+
import { Retriever } from "@/user/retriever";
|
|
16
|
+
import { Writer } from "@/user/writer";
|
|
16
17
|
|
|
17
18
|
export class Client {
|
|
18
|
-
private readonly
|
|
19
|
+
private readonly reader: Retriever;
|
|
20
|
+
private readonly writer: Writer;
|
|
19
21
|
|
|
20
22
|
constructor(client: UnaryClient) {
|
|
21
|
-
this.
|
|
23
|
+
this.writer = new Writer(client);
|
|
24
|
+
this.reader = new Retriever(client);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async create(user: NewUser): Promise<User>;
|
|
28
|
+
|
|
29
|
+
async create(users: NewUser[]): Promise<User[]>;
|
|
30
|
+
|
|
31
|
+
async create(users: NewUser | NewUser[]): Promise<User | User[]> {
|
|
32
|
+
const isMany = Array.isArray(users);
|
|
33
|
+
const res = await this.writer.create(users);
|
|
34
|
+
return isMany ? res : res[0];
|
|
22
35
|
}
|
|
23
36
|
|
|
24
|
-
async
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
35
|
-
return
|
|
37
|
+
async changeUsername(key: Key, newUsername: string): Promise<void> {
|
|
38
|
+
await this.writer.changeUsername(key, newUsername);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async retrieve(key: Key): Promise<User>;
|
|
42
|
+
|
|
43
|
+
async retrieve(keys: Key[]): Promise<User[]>;
|
|
44
|
+
|
|
45
|
+
async retrieve(keys: Key | Key[]): Promise<User | User[]> {
|
|
46
|
+
const isMany = Array.isArray(keys);
|
|
47
|
+
const res = await this.reader.retrieve({ keys: toArray(keys) });
|
|
48
|
+
if (isMany) return res;
|
|
49
|
+
if (res.length === 0) throw new NotFoundError(`No user with key ${keys} found`);
|
|
50
|
+
if (res.length > 1)
|
|
51
|
+
throw new MultipleFoundError(`Multiple users found with key ${keys}`);
|
|
52
|
+
return res[0];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async retrieveByName(username: string): Promise<User>;
|
|
56
|
+
|
|
57
|
+
async retrieveByName(usernames: string[]): Promise<User[]>;
|
|
58
|
+
|
|
59
|
+
async retrieveByName(usernames: string | string[]): Promise<User | User[]> {
|
|
60
|
+
const isMany = Array.isArray(usernames);
|
|
61
|
+
const res = await this.reader.retrieve({ usernames: toArray(usernames) });
|
|
62
|
+
if (isMany) return res;
|
|
63
|
+
if (res.length === 0)
|
|
64
|
+
throw new NotFoundError(`No user with username ${usernames} found`);
|
|
65
|
+
if (res.length > 1)
|
|
66
|
+
throw new MultipleFoundError(`Multiple users found with username ${usernames}`);
|
|
67
|
+
return res[0];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async rename(key: Key, firstName?: string, lastName?: string): Promise<void> {
|
|
71
|
+
await this.writer.rename(key, firstName, lastName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async delete(key: Key): Promise<void>;
|
|
75
|
+
|
|
76
|
+
async delete(keys: Key[]): Promise<void>;
|
|
77
|
+
|
|
78
|
+
async delete(keys: Key | Key[]): Promise<void> {
|
|
79
|
+
await this.writer.delete(keys);
|
|
36
80
|
}
|
|
37
81
|
}
|
package/src/user/payload.ts
CHANGED
|
@@ -12,17 +12,24 @@ import { z } from "zod";
|
|
|
12
12
|
import { ontology } from "@/ontology";
|
|
13
13
|
|
|
14
14
|
export const keyZ = z.string().uuid();
|
|
15
|
-
|
|
16
15
|
export type Key = z.infer<typeof keyZ>;
|
|
17
16
|
|
|
18
|
-
export const
|
|
19
|
-
key:
|
|
20
|
-
username: z.string(),
|
|
17
|
+
export const userZ = z.object({
|
|
18
|
+
key: keyZ,
|
|
19
|
+
username: z.string().min(1),
|
|
20
|
+
firstName: z.string(),
|
|
21
|
+
lastName: z.string(),
|
|
22
|
+
rootUser: z.boolean(),
|
|
21
23
|
});
|
|
24
|
+
export type User = z.infer<typeof userZ>;
|
|
22
25
|
|
|
23
|
-
export
|
|
26
|
+
export const newUserZ = userZ
|
|
27
|
+
.partial({ key: true, firstName: true, lastName: true })
|
|
28
|
+
.omit({ rootUser: true })
|
|
29
|
+
.extend({ password: z.string().min(1) });
|
|
30
|
+
export type NewUser = z.infer<typeof newUserZ>;
|
|
24
31
|
|
|
25
|
-
export const
|
|
32
|
+
export const ONTOLOGY_TYPE: ontology.ResourceType = "user";
|
|
26
33
|
|
|
27
34
|
export const ontologyID = (key: Key): ontology.ID =>
|
|
28
|
-
new ontology.ID({ type:
|
|
35
|
+
new ontology.ID({ type: ONTOLOGY_TYPE, key });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright 2024 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import { keyZ, type User, userZ } from "@/user/payload";
|
|
14
|
+
import { nullableArrayZ } from "@/util/zod";
|
|
15
|
+
|
|
16
|
+
const reqZ = z.object({
|
|
17
|
+
keys: keyZ.array().optional(),
|
|
18
|
+
usernames: z.string().array().optional(),
|
|
19
|
+
});
|
|
20
|
+
type Request = z.infer<typeof reqZ>;
|
|
21
|
+
const resZ = z.object({ users: nullableArrayZ(userZ) });
|
|
22
|
+
const ENDPOINT = "/user/retrieve";
|
|
23
|
+
|
|
24
|
+
export class Retriever {
|
|
25
|
+
private readonly client: UnaryClient;
|
|
26
|
+
|
|
27
|
+
constructor(client: UnaryClient) {
|
|
28
|
+
this.client = client;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async retrieve(req: Request): Promise<User[]> {
|
|
32
|
+
const res = await sendRequired<typeof reqZ, typeof resZ>(
|
|
33
|
+
this.client,
|
|
34
|
+
ENDPOINT,
|
|
35
|
+
req,
|
|
36
|
+
reqZ,
|
|
37
|
+
resZ,
|
|
38
|
+
);
|
|
39
|
+
return res.users;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// Copyright 2024 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
import { id } from "@synnaxlabs/x";
|
|
11
|
+
import { describe, expect, test } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { AuthError, NotFoundError } from "@/errors";
|
|
14
|
+
import { newClient } from "@/setupspecs";
|
|
15
|
+
import { type user } from "@/user";
|
|
16
|
+
|
|
17
|
+
type SortType = { username: string };
|
|
18
|
+
|
|
19
|
+
const sort = (a: SortType, b: SortType) => a.username.localeCompare(b.username);
|
|
20
|
+
|
|
21
|
+
const client = newClient();
|
|
22
|
+
|
|
23
|
+
const userOne: user.NewUser = {
|
|
24
|
+
username: id.id(),
|
|
25
|
+
password: "test",
|
|
26
|
+
firstName: "George",
|
|
27
|
+
lastName: "Washington",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const userTwo: user.NewUser = { username: id.id(), password: "test" };
|
|
31
|
+
|
|
32
|
+
const userThree: user.NewUser = {
|
|
33
|
+
username: id.id(),
|
|
34
|
+
password: "test",
|
|
35
|
+
firstName: "John",
|
|
36
|
+
lastName: "Adams",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const userArray: user.NewUser[] = [
|
|
40
|
+
{ username: id.id(), password: "secondTest", firstName: "Steve" },
|
|
41
|
+
{ username: id.id(), password: "testArray" },
|
|
42
|
+
].sort(sort);
|
|
43
|
+
|
|
44
|
+
describe("User", () => {
|
|
45
|
+
describe("Create", () => {
|
|
46
|
+
describe("One", () => {
|
|
47
|
+
test("with a name", async () => {
|
|
48
|
+
const res = await client.user.create(userOne);
|
|
49
|
+
expect(res.username).toEqual(userOne.username);
|
|
50
|
+
expect(res.key).not.toEqual("");
|
|
51
|
+
expect(res.firstName).toEqual(userOne.firstName);
|
|
52
|
+
expect(res.lastName).toEqual(userOne.lastName);
|
|
53
|
+
userOne.key = res.key;
|
|
54
|
+
});
|
|
55
|
+
test("with no name", async () => {
|
|
56
|
+
const res = await client.user.create(userTwo);
|
|
57
|
+
expect(res.username).toEqual(userTwo.username);
|
|
58
|
+
expect(res.key).not.toEqual("");
|
|
59
|
+
userTwo.key = res.key;
|
|
60
|
+
expect(res.firstName).toEqual("");
|
|
61
|
+
expect(res.lastName).toEqual("");
|
|
62
|
+
});
|
|
63
|
+
test("Repeated username", async () =>
|
|
64
|
+
await expect(
|
|
65
|
+
client.user.create({ username: userOne.username, password: "test" }),
|
|
66
|
+
).rejects.toThrow(AuthError));
|
|
67
|
+
});
|
|
68
|
+
describe("Many", () => {
|
|
69
|
+
test("array empty", async () => {
|
|
70
|
+
const res = await client.user.create([]);
|
|
71
|
+
expect(res).toHaveLength(0);
|
|
72
|
+
});
|
|
73
|
+
test("array is one", async () => {
|
|
74
|
+
const res = await client.user.create([userThree]);
|
|
75
|
+
expect(res).toHaveLength(1);
|
|
76
|
+
expect(res[0].username).toEqual(userThree.username);
|
|
77
|
+
expect(res[0].key).not.toEqual("");
|
|
78
|
+
userThree.key = res[0].key;
|
|
79
|
+
expect(res[0].firstName).toEqual(userThree.firstName);
|
|
80
|
+
expect(res[0].lastName).toEqual(userThree.lastName);
|
|
81
|
+
});
|
|
82
|
+
test("array not empty", async () => {
|
|
83
|
+
const res = await client.user.create(userArray);
|
|
84
|
+
expect(res).toHaveLength(2);
|
|
85
|
+
userArray.forEach((u, i) => {
|
|
86
|
+
expect(res[i].username).toEqual(u.username);
|
|
87
|
+
expect(res[i].key).not.toEqual("");
|
|
88
|
+
userArray[i].key = res[i].key;
|
|
89
|
+
expect(res[i].firstName).toEqual(u.firstName ?? "");
|
|
90
|
+
expect(res[i].lastName).toEqual(u.lastName ?? "");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
test("Repeated username", async () =>
|
|
94
|
+
await expect(client.user.create([userOne, userTwo])).rejects.toThrow(
|
|
95
|
+
AuthError,
|
|
96
|
+
));
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("Retrieve", () => {
|
|
100
|
+
describe("by name", () => {
|
|
101
|
+
describe("one", () => {
|
|
102
|
+
test("found", async () => {
|
|
103
|
+
const res = await client.user.retrieveByName(userOne.username);
|
|
104
|
+
expect(res.username).toEqual(userOne.username);
|
|
105
|
+
expect(res.key).toEqual(userOne.key);
|
|
106
|
+
expect(res.firstName).toEqual(userOne.firstName);
|
|
107
|
+
expect(res.lastName).toEqual(userOne.lastName);
|
|
108
|
+
});
|
|
109
|
+
test("not found", async () =>
|
|
110
|
+
await expect(client.user.retrieveByName(id.id())).rejects.toThrow(
|
|
111
|
+
NotFoundError,
|
|
112
|
+
));
|
|
113
|
+
});
|
|
114
|
+
describe("many", () => {
|
|
115
|
+
test("found", async () => {
|
|
116
|
+
const res = await client.user.retrieveByName(
|
|
117
|
+
userArray.map((u) => u.username),
|
|
118
|
+
);
|
|
119
|
+
expect(res.sort(sort)).toHaveLength(2);
|
|
120
|
+
res.forEach((u, i) => {
|
|
121
|
+
expect(u.username).toEqual(userArray[i].username);
|
|
122
|
+
expect(u.key).toEqual(userArray[i].key);
|
|
123
|
+
expect(u.firstName).toEqual(userArray[i].firstName ?? "");
|
|
124
|
+
expect(u.lastName).toEqual(userArray[i].lastName ?? "");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
test("not found", async () => {
|
|
128
|
+
const res = await client.user.retrieveByName([id.id()]);
|
|
129
|
+
expect(res).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
test("extra names getting deleted", async () => {
|
|
132
|
+
const res = await client.user.retrieveByName([
|
|
133
|
+
...userArray.map((u) => u.username),
|
|
134
|
+
id.id(),
|
|
135
|
+
]);
|
|
136
|
+
expect(res.sort(sort)).toHaveLength(2);
|
|
137
|
+
res.forEach((u, i) => {
|
|
138
|
+
expect(u.username).toEqual(userArray[i].username);
|
|
139
|
+
expect(u.key).toEqual(userArray[i].key);
|
|
140
|
+
expect(u.firstName).toEqual(userArray[i].firstName ?? "");
|
|
141
|
+
expect(u.lastName).toEqual(userArray[i].lastName ?? "");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
test("calling with no names", async () => {
|
|
145
|
+
const res = await client.user.retrieveByName([]);
|
|
146
|
+
const usernames = res.map((u) => u.username);
|
|
147
|
+
expect(usernames).toContain(userOne.username);
|
|
148
|
+
expect(usernames).toContain(userTwo.username);
|
|
149
|
+
expect(usernames).toContain(userThree.username);
|
|
150
|
+
userArray.forEach((u) => expect(usernames).toContain(u.username));
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe("by key", () => {
|
|
155
|
+
describe("one", () => {
|
|
156
|
+
test("found", async () => {
|
|
157
|
+
const res = await client.user.retrieve(userOne.key as string);
|
|
158
|
+
expect(res.username).toEqual(userOne.username);
|
|
159
|
+
expect(res.key).toEqual(userOne.key);
|
|
160
|
+
expect(res.firstName).toEqual(userOne.firstName);
|
|
161
|
+
expect(res.lastName).toEqual(userOne.lastName);
|
|
162
|
+
});
|
|
163
|
+
test("not found", async () => {
|
|
164
|
+
await expect(
|
|
165
|
+
client.user.delete(userOne.key as string),
|
|
166
|
+
).resolves.toBeUndefined();
|
|
167
|
+
await expect(client.user.retrieve(userOne.key as string)).rejects.toThrow(
|
|
168
|
+
NotFoundError,
|
|
169
|
+
);
|
|
170
|
+
const u = await client.user.create(userOne);
|
|
171
|
+
userOne.key = u.key;
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe("many", () => {
|
|
175
|
+
test("found", async () => {
|
|
176
|
+
const res = await client.user.retrieve(userArray.map((u) => u.key as string));
|
|
177
|
+
expect(res.sort(sort)).toHaveLength(2);
|
|
178
|
+
res.forEach((u, i) => {
|
|
179
|
+
expect(u.username).toEqual(userArray[i].username);
|
|
180
|
+
expect(u.key).toEqual(userArray[i].key);
|
|
181
|
+
expect(u.firstName).toEqual(userArray[i].firstName ?? "");
|
|
182
|
+
expect(u.lastName).toEqual(userArray[i].lastName ?? "");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
test("not found", async () => {
|
|
186
|
+
for (const u of userArray) {
|
|
187
|
+
await expect(client.user.delete(u.key as string)).resolves.toBeUndefined();
|
|
188
|
+
}
|
|
189
|
+
await expect(
|
|
190
|
+
client.user.retrieve(userArray.map((u) => u.key as string)),
|
|
191
|
+
).rejects.toThrow(NotFoundError);
|
|
192
|
+
// cleanup
|
|
193
|
+
const users = await client.user.create(userArray);
|
|
194
|
+
users.forEach((u, i) => (userArray[i].key = u.key));
|
|
195
|
+
});
|
|
196
|
+
test("all", async () => {
|
|
197
|
+
const res = await client.user.retrieve([]);
|
|
198
|
+
const usernames = res.map((u) => u.username);
|
|
199
|
+
expect(usernames).toContain(userOne.username);
|
|
200
|
+
expect(usernames).toContain(userTwo.username);
|
|
201
|
+
expect(usernames).toContain(userThree.username);
|
|
202
|
+
userArray.forEach((u) => expect(usernames).toContain(u.username));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe("Change Username", () => {
|
|
208
|
+
test("Successful", async () => {
|
|
209
|
+
const newUsername = id.id();
|
|
210
|
+
await expect(
|
|
211
|
+
client.user.changeUsername(userOne.key as string, newUsername),
|
|
212
|
+
).resolves.toBeUndefined();
|
|
213
|
+
const res = await client.user.retrieveByName(newUsername);
|
|
214
|
+
expect(res.username).toEqual(newUsername);
|
|
215
|
+
expect(res.key).not.toEqual("");
|
|
216
|
+
expect(res.firstName).toEqual(userOne.firstName);
|
|
217
|
+
expect(res.lastName).toEqual(userOne.lastName);
|
|
218
|
+
userOne.username = newUsername;
|
|
219
|
+
});
|
|
220
|
+
test("Unsuccessful", async () =>
|
|
221
|
+
await expect(
|
|
222
|
+
client.user.changeUsername(userTwo.key as string, userOne.username),
|
|
223
|
+
).rejects.toThrow(AuthError));
|
|
224
|
+
test("Repeated usernames fail", async () => {
|
|
225
|
+
const oldUsername = id.id();
|
|
226
|
+
const user = await client.user.create({
|
|
227
|
+
username: oldUsername,
|
|
228
|
+
password: "test",
|
|
229
|
+
});
|
|
230
|
+
const newUsername = id.id();
|
|
231
|
+
await client.user.changeUsername(user.key, newUsername);
|
|
232
|
+
await expect(
|
|
233
|
+
client.user.create({ username: newUsername, password: "test" }),
|
|
234
|
+
).rejects.toThrow(AuthError);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
describe("Change Name", () => {
|
|
238
|
+
test("Successful", async () => {
|
|
239
|
+
await expect(
|
|
240
|
+
client.user.rename(userOne.key as string, "Thomas", "Jefferson"),
|
|
241
|
+
).resolves.toBeUndefined();
|
|
242
|
+
const res = await client.user.retrieve(userOne.key as string);
|
|
243
|
+
expect(res.username).toEqual(userOne.username);
|
|
244
|
+
expect(res.key).toEqual(userOne.key);
|
|
245
|
+
expect(res.firstName).toEqual("Thomas");
|
|
246
|
+
expect(res.lastName).toEqual("Jefferson");
|
|
247
|
+
userOne.firstName = "Thomas";
|
|
248
|
+
userOne.lastName = "Jefferson";
|
|
249
|
+
});
|
|
250
|
+
test("Only one name", async () => {
|
|
251
|
+
await expect(
|
|
252
|
+
client.user.rename(userOne.key as string, "James"),
|
|
253
|
+
).resolves.toBeUndefined();
|
|
254
|
+
const res = await client.user.retrieve(userOne.key as string);
|
|
255
|
+
expect(res.username).toEqual(userOne.username);
|
|
256
|
+
expect(res.key).toEqual(userOne.key);
|
|
257
|
+
expect(res.firstName).toEqual("James");
|
|
258
|
+
expect(res.lastName).toEqual(userOne.lastName);
|
|
259
|
+
userOne.firstName = "James";
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
describe("Delete", () => {
|
|
263
|
+
test("one that exists", async () => {
|
|
264
|
+
await expect(client.user.delete(userOne.key as string)).resolves.toBeUndefined();
|
|
265
|
+
await expect(client.user.retrieve(userOne.key as string)).rejects.toThrow(
|
|
266
|
+
NotFoundError,
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
test("many that exist", async () => {
|
|
270
|
+
await expect(
|
|
271
|
+
client.user.delete(userArray.map((u) => u.key as string)),
|
|
272
|
+
).resolves.toBeUndefined();
|
|
273
|
+
await expect(
|
|
274
|
+
client.user.retrieve(userArray.map((u) => u.key as string)),
|
|
275
|
+
).rejects.toThrow(NotFoundError);
|
|
276
|
+
});
|
|
277
|
+
test("one that doesn't exist", async () => {
|
|
278
|
+
await expect(client.user.delete(userOne.key as string)).resolves.toBeUndefined();
|
|
279
|
+
});
|
|
280
|
+
test("many where some don't exist", async () => {
|
|
281
|
+
await expect(
|
|
282
|
+
client.user.delete([userOne.key as string, userTwo.key as string]),
|
|
283
|
+
).resolves.toBeUndefined();
|
|
284
|
+
await expect(client.user.retrieve(userTwo.key as string)).rejects.toThrow(
|
|
285
|
+
NotFoundError,
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|