@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.
Files changed (115) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/api/client.api.md +615 -261
  3. package/dist/access/client.d.ts +2 -7
  4. package/dist/access/client.d.ts.map +1 -1
  5. package/dist/access/payload.d.ts +7 -102
  6. package/dist/access/payload.d.ts.map +1 -1
  7. package/dist/access/policy/client.d.ts +17 -0
  8. package/dist/access/policy/client.d.ts.map +1 -0
  9. package/dist/access/policy/external.d.ts +3 -0
  10. package/dist/access/policy/external.d.ts.map +1 -0
  11. package/dist/access/policy/index.d.ts +2 -0
  12. package/dist/access/policy/index.d.ts.map +1 -0
  13. package/dist/access/policy/payload.d.ts +163 -0
  14. package/dist/access/policy/payload.d.ts.map +1 -0
  15. package/dist/access/policy/policy.spec.d.ts +2 -0
  16. package/dist/access/policy/policy.spec.d.ts.map +1 -0
  17. package/dist/access/policy/retriever.d.ts +36 -0
  18. package/dist/access/policy/retriever.d.ts.map +1 -0
  19. package/dist/access/policy/writer.d.ts +9 -0
  20. package/dist/access/policy/writer.d.ts.map +1 -0
  21. package/dist/auth/auth.d.ts +6 -30
  22. package/dist/auth/auth.d.ts.map +1 -1
  23. package/dist/channel/payload.d.ts +17 -17
  24. package/dist/channel/payload.d.ts.map +1 -1
  25. package/dist/channel/retriever.d.ts +8 -8
  26. package/dist/client.cjs +31 -21
  27. package/dist/client.js +2962 -2233
  28. package/dist/framer/client.d.ts +4 -1
  29. package/dist/framer/client.d.ts.map +1 -1
  30. package/dist/framer/frame.d.ts +27 -80
  31. package/dist/framer/frame.d.ts.map +1 -1
  32. package/dist/framer/streamer.d.ts +3 -1
  33. package/dist/framer/streamer.d.ts.map +1 -1
  34. package/dist/framer/writer.d.ts +24 -16
  35. package/dist/framer/writer.d.ts.map +1 -1
  36. package/dist/hardware/device/client.d.ts +2 -2
  37. package/dist/hardware/device/payload.d.ts +1 -1
  38. package/dist/hardware/device/payload.d.ts.map +1 -1
  39. package/dist/hardware/rack/payload.d.ts +1 -1
  40. package/dist/hardware/rack/payload.d.ts.map +1 -1
  41. package/dist/hardware/task/client.d.ts +2 -2
  42. package/dist/hardware/task/ni/types.d.ts +16 -16
  43. package/dist/hardware/task/payload.d.ts +13 -13
  44. package/dist/hardware/task/payload.d.ts.map +1 -1
  45. package/dist/index.d.ts +6 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/label/payload.d.ts +1 -1
  48. package/dist/label/payload.d.ts.map +1 -1
  49. package/dist/label/writer.d.ts +5 -5
  50. package/dist/ontology/client.d.ts +32 -30
  51. package/dist/ontology/client.d.ts.map +1 -1
  52. package/dist/ontology/payload.d.ts +62 -63
  53. package/dist/ontology/payload.d.ts.map +1 -1
  54. package/dist/ranger/payload.d.ts +2 -2
  55. package/dist/ranger/payload.d.ts.map +1 -1
  56. package/dist/ranger/writer.d.ts +5 -5
  57. package/dist/user/client.d.ts +13 -3
  58. package/dist/user/client.d.ts.map +1 -1
  59. package/dist/user/payload.d.ts +34 -3
  60. package/dist/user/payload.d.ts.map +1 -1
  61. package/dist/user/retriever.d.ts +21 -0
  62. package/dist/user/retriever.d.ts.map +1 -0
  63. package/dist/user/user.spec.d.ts +2 -0
  64. package/dist/user/user.spec.d.ts.map +1 -0
  65. package/dist/user/writer.d.ts +11 -0
  66. package/dist/user/writer.d.ts.map +1 -0
  67. package/dist/workspace/lineplot/payload.d.ts +1 -1
  68. package/dist/workspace/lineplot/payload.d.ts.map +1 -1
  69. package/dist/workspace/payload.d.ts +1 -1
  70. package/dist/workspace/payload.d.ts.map +1 -1
  71. package/dist/workspace/schematic/client.d.ts.map +1 -1
  72. package/dist/workspace/schematic/payload.d.ts +1 -1
  73. package/dist/workspace/schematic/payload.d.ts.map +1 -1
  74. package/examples/node/package-lock.json +963 -134
  75. package/examples/node/package.json +1 -1
  76. package/package.json +3 -3
  77. package/src/access/client.ts +4 -70
  78. package/src/access/payload.ts +14 -24
  79. package/src/access/policy/client.ts +65 -0
  80. package/src/access/policy/external.ts +11 -0
  81. package/src/access/policy/index.ts +10 -0
  82. package/src/access/policy/payload.ts +45 -0
  83. package/src/access/policy/policy.spec.ts +331 -0
  84. package/src/access/policy/retriever.ts +43 -0
  85. package/src/access/policy/writer.ts +65 -0
  86. package/src/auth/auth.ts +32 -10
  87. package/src/channel/payload.ts +2 -2
  88. package/src/framer/client.ts +7 -1
  89. package/src/framer/frame.spec.ts +21 -12
  90. package/src/framer/frame.ts +9 -24
  91. package/src/framer/streamer.spec.ts +48 -0
  92. package/src/framer/streamer.ts +7 -4
  93. package/src/framer/writer.ts +0 -2
  94. package/src/hardware/device/payload.ts +2 -2
  95. package/src/hardware/rack/payload.ts +2 -2
  96. package/src/hardware/task/payload.ts +2 -2
  97. package/src/index.ts +16 -13
  98. package/src/label/payload.ts +2 -2
  99. package/src/ontology/client.ts +35 -34
  100. package/src/ontology/payload.ts +28 -35
  101. package/src/ranger/payload.ts +5 -7
  102. package/src/setupspecs.ts +2 -2
  103. package/src/user/client.ts +63 -19
  104. package/src/user/payload.ts +14 -7
  105. package/src/user/retriever.ts +41 -0
  106. package/src/user/user.spec.ts +289 -0
  107. package/src/user/writer.ts +91 -0
  108. package/src/workspace/lineplot/payload.ts +2 -2
  109. package/src/workspace/payload.ts +2 -2
  110. package/src/workspace/schematic/client.ts +1 -1
  111. package/src/workspace/schematic/payload.ts +2 -2
  112. package/src/workspace/workspace.spec.ts +1 -1
  113. package/dist/access/access.spec.d.ts +0 -2
  114. package/dist/access/access.spec.d.ts.map +0 -1
  115. package/src/access/access.spec.ts +0 -276
@@ -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 BuiltinOntologyType = "builtin" as ResourceType;
42
- export const ClusterOntologyType = "cluster" as ResourceType;
43
- export const NodeOntologyType = "node" as ResourceType;
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 as ResourceType, key };
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
- } else if (typeof args === "string") {
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
- } else {
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
- get payload(): z.infer<typeof idZ> {
81
- return {
82
- type: this.type,
83
- key: this.key,
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([crudeIDZ, z.instanceof(ID)]).transform((v) => new ID(v));
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 => {
@@ -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 RangeOntologyType = "range" as ontology.ResourceType;
81
- export const RangeAliasOntologyType = "range-alias" as ontology.ResourceType;
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: RangeOntologyType, key: key });
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: RangeAliasOntologyType, key: key });
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 = {};
@@ -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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
10
+ import { type UnaryClient } from "@synnaxlabs/freighter";
11
+ import { toArray } from "@synnaxlabs/x";
11
12
 
12
- import { insecureCredentialsZ, tokenResponseZ } from "@/auth/auth";
13
- import { Payload } from "@/user/payload";
14
-
15
- const REGISTER_ENDPOINT = "/user/register";
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 client: UnaryClient;
19
+ private readonly reader: Retriever;
20
+ private readonly writer: Writer;
19
21
 
20
22
  constructor(client: UnaryClient) {
21
- this.client = client;
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 register(username: string, password: string): Promise<Payload> {
25
- const { user } = await sendRequired<
26
- typeof insecureCredentialsZ,
27
- typeof tokenResponseZ
28
- >(
29
- this.client,
30
- REGISTER_ENDPOINT,
31
- { username: username, password: password },
32
- insecureCredentialsZ,
33
- tokenResponseZ,
34
- );
35
- return user;
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
  }
@@ -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 payloadZ = z.object({
19
- key: z.string(),
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 type Payload = z.infer<typeof payloadZ>;
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 UserOntologyType = "user" as ontology.ResourceType;
32
+ export const ONTOLOGY_TYPE: ontology.ResourceType = "user";
26
33
 
27
34
  export const ontologyID = (key: Key): ontology.ID =>
28
- new ontology.ID({ type: UserOntologyType, key });
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
+ });