@synnaxlabs/client 0.48.0 → 0.49.2
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/dist/client.cjs +33 -31
- package/dist/client.js +6522 -6167
- package/dist/src/access/client.d.ts +3 -1
- package/dist/src/access/client.d.ts.map +1 -1
- package/dist/src/access/enforce.d.ts +35 -0
- package/dist/src/access/enforce.d.ts.map +1 -0
- package/dist/src/access/enforce.spec.d.ts +2 -0
- package/dist/src/access/enforce.spec.d.ts.map +1 -0
- package/dist/src/access/external.d.ts +3 -0
- package/dist/src/access/external.d.ts.map +1 -1
- package/dist/src/access/payload.d.ts +0 -6
- package/dist/src/access/payload.d.ts.map +1 -1
- package/dist/src/access/policy/access.spec.d.ts +2 -0
- package/dist/src/access/policy/access.spec.d.ts.map +1 -0
- package/dist/src/access/policy/client.d.ts +485 -31
- package/dist/src/access/policy/client.d.ts.map +1 -1
- package/dist/src/access/policy/payload.d.ts +36 -113
- package/dist/src/access/policy/payload.d.ts.map +1 -1
- package/dist/src/access/role/client.d.ts +135 -0
- package/dist/src/access/role/client.d.ts.map +1 -0
- package/dist/src/access/role/external.d.ts.map +1 -0
- package/dist/src/access/role/index.d.ts +2 -0
- package/dist/src/access/role/index.d.ts.map +1 -0
- package/dist/src/access/role/payload.d.ts +27 -0
- package/dist/src/access/role/payload.d.ts.map +1 -0
- package/dist/src/access/role/role.spec.d.ts +2 -0
- package/dist/src/access/role/role.spec.d.ts.map +1 -0
- package/dist/src/arc/access.spec.d.ts +2 -0
- package/dist/src/arc/access.spec.d.ts.map +1 -0
- package/dist/src/arc/client.d.ts +5 -14
- package/dist/src/arc/client.d.ts.map +1 -1
- package/dist/src/arc/payload.d.ts +11 -2
- package/dist/src/arc/payload.d.ts.map +1 -1
- package/dist/src/auth/auth.d.ts +5 -3
- package/dist/src/auth/auth.d.ts.map +1 -1
- package/dist/src/channel/access.spec.d.ts +2 -0
- package/dist/src/channel/access.spec.d.ts.map +1 -0
- package/dist/src/channel/client.d.ts +0 -1
- package/dist/src/channel/client.d.ts.map +1 -1
- package/dist/src/channel/payload.d.ts +18 -8
- package/dist/src/channel/payload.d.ts.map +1 -1
- package/dist/src/channel/payload.spec.d.ts +2 -0
- package/dist/src/channel/payload.spec.d.ts.map +1 -0
- package/dist/src/channel/retriever.d.ts +4 -6
- package/dist/src/channel/retriever.d.ts.map +1 -1
- package/dist/src/channel/writer.d.ts.map +1 -1
- package/dist/src/client.d.ts +9 -5
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/device/access.spec.d.ts +2 -0
- package/dist/src/device/access.spec.d.ts.map +1 -0
- package/dist/src/{hardware/device → device}/client.d.ts +14 -7
- package/dist/src/device/client.d.ts.map +1 -0
- package/dist/src/device/device.spec.d.ts.map +1 -0
- package/dist/src/device/external.d.ts.map +1 -0
- package/dist/src/device/index.d.ts.map +1 -0
- package/dist/src/{hardware/device → device}/payload.d.ts +1 -1
- package/dist/src/device/payload.d.ts.map +1 -0
- package/dist/src/errors.d.ts +3 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/framer/client.d.ts +11 -1
- package/dist/src/framer/client.d.ts.map +1 -1
- package/dist/src/framer/frame.d.ts +10 -5
- package/dist/src/framer/frame.d.ts.map +1 -1
- package/dist/src/framer/iterator.d.ts +3 -3
- package/dist/src/framer/reader.d.ts +16 -0
- package/dist/src/framer/reader.d.ts.map +1 -0
- package/dist/src/framer/reader.spec.d.ts +2 -0
- package/dist/src/framer/reader.spec.d.ts.map +1 -0
- package/dist/src/framer/streamer.d.ts +24 -21
- package/dist/src/framer/streamer.d.ts.map +1 -1
- package/dist/src/framer/writer.d.ts +13 -13
- package/dist/src/index.d.ts +4 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/label/access.spec.d.ts +2 -0
- package/dist/src/label/access.spec.d.ts.map +1 -0
- package/dist/src/label/client.d.ts +20 -11
- package/dist/src/label/client.d.ts.map +1 -1
- package/dist/src/ontology/client.d.ts +6 -6
- package/dist/src/ontology/client.d.ts.map +1 -1
- package/dist/src/ontology/group/access.spec.d.ts +2 -0
- package/dist/src/ontology/group/access.spec.d.ts.map +1 -0
- package/dist/src/ontology/group/client.d.ts +2 -2
- package/dist/src/ontology/group/client.d.ts.map +1 -1
- package/dist/src/ontology/group/payload.d.ts +1 -2
- package/dist/src/ontology/group/payload.d.ts.map +1 -1
- package/dist/src/ontology/payload.d.ts +23 -17
- package/dist/src/ontology/payload.d.ts.map +1 -1
- package/dist/src/ontology/writer.d.ts +10 -10
- package/dist/src/ontology/writer.d.ts.map +1 -1
- package/dist/src/rack/access.spec.d.ts +2 -0
- package/dist/src/rack/access.spec.d.ts.map +1 -0
- package/dist/src/{hardware/rack → rack}/client.d.ts +15 -8
- package/dist/src/rack/client.d.ts.map +1 -0
- package/dist/src/rack/external.d.ts.map +1 -0
- package/dist/src/rack/index.d.ts.map +1 -0
- package/dist/src/{hardware/rack → rack}/payload.d.ts +1 -1
- package/dist/src/rack/payload.d.ts.map +1 -0
- package/dist/src/rack/rack.spec.d.ts.map +1 -0
- package/dist/src/ranger/access.spec.d.ts +2 -0
- package/dist/src/ranger/access.spec.d.ts.map +1 -0
- package/dist/src/ranger/alias.d.ts +1 -8
- package/dist/src/ranger/alias.d.ts.map +1 -1
- package/dist/src/ranger/client.d.ts +12 -5
- package/dist/src/ranger/client.d.ts.map +1 -1
- package/dist/src/ranger/kv.d.ts +0 -3
- package/dist/src/ranger/kv.d.ts.map +1 -1
- package/dist/src/ranger/writer.d.ts +2 -2
- package/dist/src/ranger/writer.d.ts.map +1 -1
- package/dist/src/status/access.spec.d.ts +2 -0
- package/dist/src/status/access.spec.d.ts.map +1 -0
- package/dist/src/status/client.d.ts +4 -4
- package/dist/src/status/client.d.ts.map +1 -1
- package/dist/src/status/payload.d.ts +9 -2
- package/dist/src/status/payload.d.ts.map +1 -1
- package/dist/src/task/access.spec.d.ts +2 -0
- package/dist/src/task/access.spec.d.ts.map +1 -0
- package/dist/src/{hardware/task → task}/client.d.ts +26 -15
- package/dist/src/task/client.d.ts.map +1 -0
- package/dist/src/task/external.d.ts +3 -0
- package/dist/src/task/external.d.ts.map +1 -0
- package/dist/src/task/index.d.ts.map +1 -0
- package/dist/src/{hardware/task → task}/payload.d.ts +45 -6
- package/dist/src/task/payload.d.ts.map +1 -0
- package/dist/src/task/task.spec.d.ts.map +1 -0
- package/dist/src/testutil/access.d.ts +4 -0
- package/dist/src/testutil/access.d.ts.map +1 -0
- package/dist/src/transport.d.ts.map +1 -1
- package/dist/src/user/access.spec.d.ts +2 -0
- package/dist/src/user/access.spec.d.ts.map +1 -0
- package/dist/src/user/client.d.ts +10 -1
- package/dist/src/user/client.d.ts.map +1 -1
- package/dist/src/user/external.d.ts +1 -1
- package/dist/src/user/external.d.ts.map +1 -1
- package/dist/src/user/payload.d.ts.map +1 -1
- package/dist/src/workspace/access.spec.d.ts +2 -0
- package/dist/src/workspace/access.spec.d.ts.map +1 -0
- package/dist/src/workspace/client.d.ts +10 -5
- package/dist/src/workspace/client.d.ts.map +1 -1
- package/dist/src/workspace/lineplot/access.spec.d.ts +2 -0
- package/dist/src/workspace/lineplot/access.spec.d.ts.map +1 -0
- package/dist/src/workspace/lineplot/client.d.ts +8 -1
- package/dist/src/workspace/lineplot/client.d.ts.map +1 -1
- package/dist/src/workspace/log/access.spec.d.ts +2 -0
- package/dist/src/workspace/log/access.spec.d.ts.map +1 -0
- package/dist/src/workspace/log/client.d.ts +8 -1
- package/dist/src/workspace/log/client.d.ts.map +1 -1
- package/dist/src/workspace/schematic/access.spec.d.ts +2 -0
- package/dist/src/workspace/schematic/access.spec.d.ts.map +1 -0
- package/dist/src/workspace/schematic/client.d.ts +8 -1
- package/dist/src/workspace/schematic/client.d.ts.map +1 -1
- package/dist/src/workspace/schematic/symbol/access.spec.d.ts +2 -0
- package/dist/src/workspace/schematic/symbol/access.spec.d.ts.map +1 -0
- package/dist/src/workspace/schematic/symbol/client.d.ts +1 -5
- package/dist/src/workspace/schematic/symbol/client.d.ts.map +1 -1
- package/dist/src/workspace/schematic/symbol/payload.d.ts +2 -2
- package/dist/src/workspace/table/access.spec.d.ts +2 -0
- package/dist/src/workspace/table/access.spec.d.ts.map +1 -0
- package/dist/src/workspace/table/client.d.ts +8 -1
- package/dist/src/workspace/table/client.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/access/client.ts +5 -2
- package/src/access/enforce.spec.ts +189 -0
- package/src/access/enforce.ts +84 -0
- package/src/access/external.ts +3 -0
- package/src/access/payload.ts +1 -13
- package/src/access/policy/access.spec.ts +147 -0
- package/src/access/policy/client.ts +21 -25
- package/src/access/policy/payload.ts +9 -5
- package/src/access/role/client.ts +135 -0
- package/src/access/role/external.ts +11 -0
- package/src/{hardware → access/role}/index.ts +1 -1
- package/src/access/role/payload.ts +32 -0
- package/src/access/role/role.spec.ts +95 -0
- package/src/arc/access.spec.ts +143 -0
- package/src/arc/client.ts +7 -31
- package/src/arc/payload.ts +4 -0
- package/src/auth/auth.ts +33 -11
- package/src/channel/access.spec.ts +116 -0
- package/src/channel/channel.spec.ts +63 -73
- package/src/channel/client.ts +2 -8
- package/src/channel/payload.spec.ts +171 -0
- package/src/channel/payload.ts +35 -7
- package/src/channel/retriever.ts +10 -11
- package/src/channel/writer.ts +3 -7
- package/src/client.ts +14 -18
- package/src/device/access.spec.ts +159 -0
- package/src/{hardware/device → device}/client.ts +12 -21
- package/src/{hardware/device → device}/device.spec.ts +70 -34
- package/src/device/external.ts +11 -0
- package/src/{hardware/rack → device}/index.ts +1 -1
- package/src/{hardware/device → device}/payload.ts +3 -3
- package/src/errors.ts +2 -0
- package/src/framer/adapter.spec.ts +14 -14
- package/src/framer/client.spec.ts +14 -20
- package/src/framer/client.ts +15 -20
- package/src/framer/deleter.spec.ts +1 -1
- package/src/framer/frame.spec.ts +131 -0
- package/src/framer/frame.ts +10 -2
- package/src/framer/iterator.ts +3 -3
- package/src/framer/reader.spec.ts +736 -0
- package/src/framer/reader.ts +265 -0
- package/src/framer/streamer.spec.ts +100 -12
- package/src/framer/streamer.ts +29 -9
- package/src/framer/writer.spec.ts +5 -5
- package/src/index.ts +4 -5
- package/src/label/access.spec.ts +109 -0
- package/src/label/client.ts +10 -14
- package/src/ontology/client.ts +4 -6
- package/src/ontology/group/access.spec.ts +77 -0
- package/src/ontology/group/client.ts +3 -7
- package/src/ontology/group/group.spec.ts +18 -0
- package/src/ontology/group/payload.ts +2 -2
- package/src/ontology/ontology.spec.ts +2 -0
- package/src/ontology/payload.ts +18 -2
- package/src/ontology/writer.ts +3 -7
- package/src/rack/access.spec.ts +102 -0
- package/src/{hardware/rack → rack}/client.ts +14 -19
- package/src/{hardware/device/index.ts → rack/external.ts} +2 -1
- package/src/{hardware/external.ts → rack/index.ts} +1 -1
- package/src/{hardware/rack → rack}/payload.ts +2 -2
- package/src/{hardware/rack → rack}/rack.spec.ts +43 -17
- package/src/ranger/access.spec.ts +115 -0
- package/src/ranger/alias.ts +6 -14
- package/src/ranger/client.ts +13 -14
- package/src/ranger/kv.ts +7 -9
- package/src/ranger/ranger.spec.ts +4 -4
- package/src/ranger/writer.ts +3 -7
- package/src/status/access.spec.ts +129 -0
- package/src/status/client.ts +5 -9
- package/src/status/payload.ts +3 -2
- package/src/task/access.spec.ts +131 -0
- package/src/{hardware/task → task}/client.ts +50 -25
- package/src/task/external.ts +11 -0
- package/src/{hardware/task → task}/index.ts +1 -1
- package/src/{hardware/task → task}/payload.ts +22 -3
- package/src/{hardware/task → task}/task.spec.ts +197 -34
- package/src/testutil/access.ts +34 -0
- package/src/testutil/channels.ts +3 -3
- package/src/transport.ts +1 -3
- package/src/user/access.spec.ts +107 -0
- package/src/user/client.ts +10 -12
- package/src/user/external.ts +12 -1
- package/src/user/payload.ts +3 -5
- package/src/workspace/access.spec.ts +108 -0
- package/src/workspace/client.ts +11 -27
- package/src/workspace/lineplot/access.spec.ts +134 -0
- package/src/workspace/lineplot/client.ts +8 -13
- package/src/workspace/log/access.spec.ts +134 -0
- package/src/workspace/log/client.ts +8 -13
- package/src/workspace/schematic/access.spec.ts +134 -0
- package/src/workspace/schematic/client.ts +9 -18
- package/src/workspace/schematic/symbol/access.spec.ts +172 -0
- package/src/workspace/schematic/symbol/client.ts +6 -17
- package/src/workspace/schematic/symbol/payload.ts +1 -1
- package/src/workspace/table/access.spec.ts +134 -0
- package/src/workspace/table/client.ts +8 -13
- package/dist/src/access/policy/policy.spec.d.ts +0 -2
- package/dist/src/access/policy/policy.spec.d.ts.map +0 -1
- package/dist/src/hardware/client.d.ts +0 -10
- package/dist/src/hardware/client.d.ts.map +0 -1
- package/dist/src/hardware/device/client.d.ts.map +0 -1
- package/dist/src/hardware/device/device.spec.d.ts.map +0 -1
- package/dist/src/hardware/device/external.d.ts.map +0 -1
- package/dist/src/hardware/device/index.d.ts.map +0 -1
- package/dist/src/hardware/device/payload.d.ts.map +0 -1
- package/dist/src/hardware/external.d.ts +0 -2
- package/dist/src/hardware/external.d.ts.map +0 -1
- package/dist/src/hardware/index.d.ts +0 -2
- package/dist/src/hardware/index.d.ts.map +0 -1
- package/dist/src/hardware/rack/client.d.ts.map +0 -1
- package/dist/src/hardware/rack/external.d.ts.map +0 -1
- package/dist/src/hardware/rack/index.d.ts.map +0 -1
- package/dist/src/hardware/rack/payload.d.ts.map +0 -1
- package/dist/src/hardware/rack/rack.spec.d.ts.map +0 -1
- package/dist/src/hardware/task/client.d.ts.map +0 -1
- package/dist/src/hardware/task/external.d.ts.map +0 -1
- package/dist/src/hardware/task/index.d.ts.map +0 -1
- package/dist/src/hardware/task/payload.d.ts.map +0 -1
- package/dist/src/hardware/task/task.spec.d.ts.map +0 -1
- package/dist/src/user/retriever.d.ts +0 -16
- package/dist/src/user/retriever.d.ts.map +0 -1
- package/dist/src/user/writer.d.ts +0 -11
- package/dist/src/user/writer.d.ts.map +0 -1
- package/src/access/policy/policy.spec.ts +0 -329
- package/src/hardware/client.ts +0 -24
- package/src/hardware/device/external.ts +0 -11
- package/src/hardware/rack/external.ts +0 -11
- package/src/hardware/task/external.ts +0 -11
- package/src/user/retriever.ts +0 -41
- package/src/user/writer.ts +0 -84
- /package/dist/src/{hardware/device → access/role}/external.d.ts +0 -0
- /package/dist/src/{hardware/device → device}/device.spec.d.ts +0 -0
- /package/dist/src/{hardware/rack → device}/external.d.ts +0 -0
- /package/dist/src/{hardware/device → device}/index.d.ts +0 -0
- /package/dist/src/{hardware/task → rack}/external.d.ts +0 -0
- /package/dist/src/{hardware/rack → rack}/index.d.ts +0 -0
- /package/dist/src/{hardware/rack → rack}/rack.spec.d.ts +0 -0
- /package/dist/src/{hardware/task → task}/index.d.ts +0 -0
- /package/dist/src/{hardware/task → task}/task.spec.d.ts +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Copyright 2025 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 { DataType, id } from "@synnaxlabs/x";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { channel } from "@/channel";
|
|
14
|
+
import { AuthError, NotFoundError } from "@/errors";
|
|
15
|
+
import { createTestClientWithPolicy } from "@/testutil/access";
|
|
16
|
+
import { createTestClient } from "@/testutil/client";
|
|
17
|
+
|
|
18
|
+
const client = createTestClient();
|
|
19
|
+
|
|
20
|
+
describe("channel", () => {
|
|
21
|
+
describe("access control", () => {
|
|
22
|
+
it("should deny access when no matching policy exists", async () => {
|
|
23
|
+
const userClient = await createTestClientWithPolicy(client, {
|
|
24
|
+
name: "test",
|
|
25
|
+
objects: [],
|
|
26
|
+
actions: [],
|
|
27
|
+
});
|
|
28
|
+
const randomChannel = await client.channels.create({
|
|
29
|
+
name: id.create(),
|
|
30
|
+
dataType: DataType.FLOAT32,
|
|
31
|
+
virtual: true,
|
|
32
|
+
});
|
|
33
|
+
await expect(userClient.channels.retrieve(randomChannel.key)).rejects.toThrow(
|
|
34
|
+
AuthError,
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should allow the caller to retrieve channels with the correct policy", async () => {
|
|
39
|
+
const userClient = await createTestClientWithPolicy(client, {
|
|
40
|
+
name: "test",
|
|
41
|
+
objects: [channel.ontologyID(0)],
|
|
42
|
+
actions: ["retrieve"],
|
|
43
|
+
});
|
|
44
|
+
const randomChannel = await client.channels.create({
|
|
45
|
+
name: id.create(),
|
|
46
|
+
dataType: DataType.FLOAT32,
|
|
47
|
+
virtual: true,
|
|
48
|
+
});
|
|
49
|
+
const retrieved = await userClient.channels.retrieve(randomChannel.key);
|
|
50
|
+
expect(retrieved.key).toBe(randomChannel.key);
|
|
51
|
+
expect(retrieved.name).toBe(randomChannel.name);
|
|
52
|
+
expect(retrieved.dataType.equals(randomChannel.dataType)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should allow the caller to create channels with the correct policy", async () => {
|
|
56
|
+
const userClient = await createTestClientWithPolicy(client, {
|
|
57
|
+
name: "test",
|
|
58
|
+
objects: [channel.ontologyID(0)],
|
|
59
|
+
actions: ["create"],
|
|
60
|
+
});
|
|
61
|
+
await userClient.channels.create({
|
|
62
|
+
name: id.create(),
|
|
63
|
+
dataType: DataType.FLOAT32,
|
|
64
|
+
virtual: true,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should deny access when no create policy exists", async () => {
|
|
69
|
+
const userClient = await createTestClientWithPolicy(client, {
|
|
70
|
+
name: "test",
|
|
71
|
+
objects: [channel.ontologyID(0)],
|
|
72
|
+
actions: ["retrieve"],
|
|
73
|
+
});
|
|
74
|
+
await expect(
|
|
75
|
+
userClient.channels.create({
|
|
76
|
+
name: id.create(),
|
|
77
|
+
dataType: DataType.FLOAT32,
|
|
78
|
+
virtual: true,
|
|
79
|
+
}),
|
|
80
|
+
).rejects.toThrow(AuthError);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should allow the caller to delete channels with the correct policy", async () => {
|
|
84
|
+
const userClient = await createTestClientWithPolicy(client, {
|
|
85
|
+
name: "test",
|
|
86
|
+
objects: [channel.ontologyID(0)],
|
|
87
|
+
actions: ["delete"],
|
|
88
|
+
});
|
|
89
|
+
const randomChannel = await client.channels.create({
|
|
90
|
+
name: id.create(),
|
|
91
|
+
dataType: DataType.FLOAT32,
|
|
92
|
+
virtual: true,
|
|
93
|
+
});
|
|
94
|
+
await userClient.channels.delete(randomChannel.key);
|
|
95
|
+
await expect(userClient.channels.retrieve(randomChannel.key)).rejects.toThrow(
|
|
96
|
+
NotFoundError,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should deny access when no delete policy exists", async () => {
|
|
101
|
+
const userClient = await createTestClientWithPolicy(client, {
|
|
102
|
+
name: "test",
|
|
103
|
+
objects: [channel.ontologyID(0)],
|
|
104
|
+
actions: ["retrieve"],
|
|
105
|
+
});
|
|
106
|
+
const randomChannel = await client.channels.create({
|
|
107
|
+
name: id.create(),
|
|
108
|
+
dataType: DataType.FLOAT32,
|
|
109
|
+
virtual: true,
|
|
110
|
+
});
|
|
111
|
+
await expect(userClient.channels.delete(randomChannel.key)).rejects.toThrow(
|
|
112
|
+
AuthError,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
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 { DataType, id
|
|
10
|
+
import { DataType, id } from "@synnaxlabs/x";
|
|
11
11
|
import { describe, expect, it, test } from "vitest";
|
|
12
12
|
|
|
13
13
|
import { Channel } from "@/channel/client";
|
|
@@ -19,12 +19,13 @@ const client = createTestClient();
|
|
|
19
19
|
describe("Channel", () => {
|
|
20
20
|
describe("create", () => {
|
|
21
21
|
test("create one", async () => {
|
|
22
|
+
const name = id.create();
|
|
22
23
|
const channel = await client.channels.create({
|
|
23
|
-
name
|
|
24
|
+
name,
|
|
24
25
|
dataType: DataType.FLOAT32,
|
|
25
26
|
virtual: true,
|
|
26
27
|
});
|
|
27
|
-
expect(channel.name
|
|
28
|
+
expect(channel.name).toEqual(name);
|
|
28
29
|
expect(channel.leaseholder).toEqual(1);
|
|
29
30
|
expect(channel.virtual).toBe(true);
|
|
30
31
|
expect(channel.dataType).toEqual(DataType.FLOAT32);
|
|
@@ -39,7 +40,7 @@ describe("Channel", () => {
|
|
|
39
40
|
});
|
|
40
41
|
chOne = await client.channels.create(chOne);
|
|
41
42
|
let calculatedCH = new Channel({
|
|
42
|
-
name:
|
|
43
|
+
name: id.create(),
|
|
43
44
|
virtual: true,
|
|
44
45
|
dataType: DataType.FLOAT32,
|
|
45
46
|
expression: `return ${chOne.name} * 2`,
|
|
@@ -53,12 +54,15 @@ describe("Channel", () => {
|
|
|
53
54
|
test("create calculated, missing required channel", async () => {
|
|
54
55
|
try {
|
|
55
56
|
await client.channels.create({
|
|
56
|
-
name:
|
|
57
|
+
name: id.create(),
|
|
57
58
|
virtual: true,
|
|
58
59
|
dataType: DataType.FLOAT32,
|
|
59
|
-
expression:
|
|
60
|
+
expression: `return 2`,
|
|
60
61
|
});
|
|
61
62
|
} catch (e) {
|
|
63
|
+
expect((e as Error).message).toContain(
|
|
64
|
+
"calculated channels must require at least one channel",
|
|
65
|
+
);
|
|
62
66
|
expect(PathError.matches(e)).toBe(true);
|
|
63
67
|
expect((e as PathError).path).toEqual(["requires"]);
|
|
64
68
|
expect((e as PathError).error.message).contain(
|
|
@@ -69,13 +73,13 @@ describe("Channel", () => {
|
|
|
69
73
|
|
|
70
74
|
test("create index and indexed pair", async () => {
|
|
71
75
|
const one = await client.channels.create({
|
|
72
|
-
name:
|
|
76
|
+
name: id.create(),
|
|
73
77
|
isIndex: true,
|
|
74
78
|
dataType: DataType.TIMESTAMP,
|
|
75
79
|
});
|
|
76
80
|
expect(one.key).not.toEqual(0);
|
|
77
81
|
const two = await client.channels.create({
|
|
78
|
-
name:
|
|
82
|
+
name: id.create(),
|
|
79
83
|
index: one.key,
|
|
80
84
|
dataType: DataType.FLOAT32,
|
|
81
85
|
});
|
|
@@ -83,36 +87,37 @@ describe("Channel", () => {
|
|
|
83
87
|
});
|
|
84
88
|
|
|
85
89
|
test("create many", async () => {
|
|
90
|
+
const names = [id.create(), id.create()];
|
|
86
91
|
const channels = await client.channels.create([
|
|
87
|
-
{ name:
|
|
88
|
-
{ name:
|
|
92
|
+
{ name: names[0], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
|
|
93
|
+
{ name: names[1], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
|
|
89
94
|
]);
|
|
90
95
|
expect(channels.length).toEqual(2);
|
|
91
|
-
expect(channels[0].name).toEqual(
|
|
92
|
-
expect(channels[1].name).toEqual(
|
|
96
|
+
expect(channels[0].name).toEqual(names[0]);
|
|
97
|
+
expect(channels[1].name).toEqual(names[1]);
|
|
93
98
|
});
|
|
94
99
|
|
|
95
100
|
test("create instances of channels", async () => {
|
|
96
101
|
const timeIndexChannel = await client.channels.create({
|
|
97
|
-
name:
|
|
102
|
+
name: id.create(),
|
|
98
103
|
dataType: DataType.TIMESTAMP,
|
|
99
104
|
isIndex: true,
|
|
100
105
|
});
|
|
101
106
|
|
|
102
107
|
const sensorOne = new Channel({
|
|
103
|
-
name:
|
|
108
|
+
name: id.create(),
|
|
104
109
|
dataType: DataType.FLOAT32,
|
|
105
110
|
index: timeIndexChannel.key,
|
|
106
111
|
});
|
|
107
112
|
|
|
108
113
|
const sensorTwo = new Channel({
|
|
109
|
-
name:
|
|
114
|
+
name: id.create(),
|
|
110
115
|
dataType: DataType.FLOAT32,
|
|
111
116
|
index: timeIndexChannel.key,
|
|
112
117
|
});
|
|
113
118
|
|
|
114
119
|
const sensorThree = new Channel({
|
|
115
|
-
name:
|
|
120
|
+
name: id.create(),
|
|
116
121
|
dataType: DataType.FLOAT32,
|
|
117
122
|
index: timeIndexChannel.key,
|
|
118
123
|
});
|
|
@@ -122,7 +127,7 @@ describe("Channel", () => {
|
|
|
122
127
|
describe("virtual", () => {
|
|
123
128
|
it("should create a virtual channel", async () => {
|
|
124
129
|
const channel = await client.channels.create({
|
|
125
|
-
name:
|
|
130
|
+
name: id.create(),
|
|
126
131
|
dataType: DataType.JSON,
|
|
127
132
|
virtual: true,
|
|
128
133
|
});
|
|
@@ -134,7 +139,7 @@ describe("Channel", () => {
|
|
|
134
139
|
|
|
135
140
|
describe("retrieveIfNameExists", () => {
|
|
136
141
|
it("should retrieve the existing channel when it exists", async () => {
|
|
137
|
-
const name =
|
|
142
|
+
const name = id.create();
|
|
138
143
|
const channel = await client.channels.create({
|
|
139
144
|
name,
|
|
140
145
|
leaseholder: 1,
|
|
@@ -148,16 +153,15 @@ describe("Channel", () => {
|
|
|
148
153
|
expect(channelTwo.key).toEqual(channel.key);
|
|
149
154
|
});
|
|
150
155
|
it("should create a new channel when it does not exist", async () => {
|
|
151
|
-
const name = `test-${Math.random()}-${TimeStamp.now().valueOf()}`;
|
|
152
156
|
const channel = await client.channels.create({
|
|
153
|
-
name,
|
|
157
|
+
name: id.create(),
|
|
154
158
|
leaseholder: 1,
|
|
155
159
|
virtual: true,
|
|
156
160
|
dataType: DataType.FLOAT32,
|
|
157
161
|
});
|
|
158
162
|
const channelTwo = await client.channels.create(
|
|
159
163
|
{
|
|
160
|
-
name:
|
|
164
|
+
name: id.create(),
|
|
161
165
|
leaseholder: 1,
|
|
162
166
|
virtual: true,
|
|
163
167
|
dataType: DataType.FLOAT32,
|
|
@@ -167,7 +171,7 @@ describe("Channel", () => {
|
|
|
167
171
|
expect(channelTwo.key).not.toEqual(channel.key);
|
|
168
172
|
});
|
|
169
173
|
it("should retrieve and create the correct channels when creating many", async () => {
|
|
170
|
-
const name =
|
|
174
|
+
const name = id.create();
|
|
171
175
|
const channel = await client.channels.create({
|
|
172
176
|
name,
|
|
173
177
|
leaseholder: 1,
|
|
@@ -178,7 +182,7 @@ describe("Channel", () => {
|
|
|
178
182
|
[
|
|
179
183
|
{ name, leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
|
|
180
184
|
{
|
|
181
|
-
name:
|
|
185
|
+
name: id.create(),
|
|
182
186
|
leaseholder: 1,
|
|
183
187
|
virtual: true,
|
|
184
188
|
dataType: DataType.FLOAT32,
|
|
@@ -196,13 +200,13 @@ describe("Channel", () => {
|
|
|
196
200
|
describe("retrieve", () => {
|
|
197
201
|
test("retrieve by key", async () => {
|
|
198
202
|
const channel = await client.channels.create({
|
|
199
|
-
name:
|
|
203
|
+
name: id.create(),
|
|
200
204
|
leaseholder: 1,
|
|
201
205
|
virtual: true,
|
|
202
206
|
dataType: DataType.FLOAT32,
|
|
203
207
|
});
|
|
204
208
|
const retrieved = await client.channels.retrieve(channel.key);
|
|
205
|
-
expect(retrieved.name).toEqual(
|
|
209
|
+
expect(retrieved.name).toEqual(channel.name);
|
|
206
210
|
expect(retrieved.leaseholder).toEqual(1);
|
|
207
211
|
expect(retrieved.virtual).toEqual(true);
|
|
208
212
|
expect(retrieved.dataType).toEqual(DataType.FLOAT32);
|
|
@@ -213,26 +217,28 @@ describe("Channel", () => {
|
|
|
213
217
|
).rejects.toThrow(NotFoundError);
|
|
214
218
|
});
|
|
215
219
|
test("retrieve by name", async () => {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
const name = id.create();
|
|
221
|
+
await client.channels.create({
|
|
222
|
+
name,
|
|
223
|
+
leaseholder: 1,
|
|
224
|
+
virtual: true,
|
|
225
|
+
dataType: DataType.FLOAT32,
|
|
226
|
+
});
|
|
227
|
+
const retrieved = await client.channels.retrieve([name]);
|
|
228
|
+
expect(retrieved.length).toBe(1);
|
|
229
|
+
expect(retrieved[0].name).toEqual(name);
|
|
219
230
|
});
|
|
220
231
|
test("retrieve by key - not found", async () => {
|
|
221
232
|
await expect(
|
|
222
233
|
async () => await client.channels.retrieve("1-1000"),
|
|
223
234
|
).rejects.toThrow(NotFoundError);
|
|
224
235
|
});
|
|
225
|
-
test("retrieve by name", async () => {
|
|
226
|
-
const retrieved = await client.channels.retrieve(["test"]);
|
|
227
|
-
expect(retrieved.length).toBeGreaterThan(0);
|
|
228
|
-
retrieved.forEach((ch) => expect(ch.name).toEqual("test"));
|
|
229
|
-
});
|
|
230
236
|
});
|
|
231
237
|
|
|
232
238
|
describe("delete", async () => {
|
|
233
239
|
test("delete by key", async () => {
|
|
234
240
|
const channel = await client.channels.create({
|
|
235
|
-
name:
|
|
241
|
+
name: id.create(),
|
|
236
242
|
leaseholder: 1,
|
|
237
243
|
virtual: true,
|
|
238
244
|
dataType: DataType.FLOAT32,
|
|
@@ -244,12 +250,12 @@ describe("Channel", () => {
|
|
|
244
250
|
});
|
|
245
251
|
test("delete by name", async () => {
|
|
246
252
|
const channel = await client.channels.create({
|
|
247
|
-
name:
|
|
253
|
+
name: id.create(),
|
|
248
254
|
leaseholder: 1,
|
|
249
255
|
virtual: true,
|
|
250
256
|
dataType: DataType.FLOAT32,
|
|
251
257
|
});
|
|
252
|
-
await client.channels.delete([
|
|
258
|
+
await client.channels.delete([channel.name]);
|
|
253
259
|
await expect(
|
|
254
260
|
async () => await client.channels.retrieve(channel.key),
|
|
255
261
|
).rejects.toThrow(NotFoundError);
|
|
@@ -258,40 +264,43 @@ describe("Channel", () => {
|
|
|
258
264
|
describe("rename", async () => {
|
|
259
265
|
test("single rename", async () => {
|
|
260
266
|
const channel = await client.channels.create({
|
|
261
|
-
name:
|
|
267
|
+
name: id.create(),
|
|
262
268
|
leaseholder: 1,
|
|
263
269
|
virtual: true,
|
|
264
270
|
dataType: DataType.FLOAT32,
|
|
265
271
|
});
|
|
266
|
-
|
|
272
|
+
const newName = id.create();
|
|
273
|
+
await client.channels.rename(channel.key, newName);
|
|
267
274
|
const renamed = await client.channels.retrieve(channel.key);
|
|
268
|
-
expect(renamed.name).toEqual(
|
|
275
|
+
expect(renamed.name).toEqual(newName);
|
|
269
276
|
});
|
|
270
277
|
test("multiple rename", async () => {
|
|
278
|
+
const names = [id.create(), id.create()];
|
|
271
279
|
const channels = await client.channels.create([
|
|
272
|
-
{ name:
|
|
273
|
-
{ name:
|
|
280
|
+
{ name: names[0], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
|
|
281
|
+
{ name: names[1], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
|
|
274
282
|
]);
|
|
275
283
|
// Retrieve channels here to ensure we check for cache invalidation
|
|
276
284
|
const initial = await client.channels.retrieve(channels.map((c) => c.key));
|
|
277
|
-
expect(initial[0].name).toEqual(
|
|
278
|
-
expect(initial[1].name).toEqual(
|
|
285
|
+
expect(initial[0].name).toEqual(names[0]);
|
|
286
|
+
expect(initial[1].name).toEqual(names[1]);
|
|
287
|
+
const newNames = [id.create(), id.create()];
|
|
279
288
|
await client.channels.rename(
|
|
280
289
|
channels.map((c) => c.key),
|
|
281
|
-
|
|
290
|
+
newNames,
|
|
282
291
|
);
|
|
283
292
|
const renamed = await client.channels.retrieve(channels.map((c) => c.key));
|
|
284
|
-
expect(renamed[0].name).toEqual(
|
|
285
|
-
expect(renamed[1].name).toEqual(
|
|
293
|
+
expect(renamed[0].name).toEqual(newNames[0]);
|
|
294
|
+
expect(renamed[1].name).toEqual(newNames[1]);
|
|
286
295
|
});
|
|
287
296
|
});
|
|
288
297
|
|
|
289
298
|
describe("update calculations", () => {
|
|
290
299
|
test("update virtual channel expression", async () => {
|
|
291
300
|
const channel = await client.channels.create({
|
|
292
|
-
name:
|
|
293
|
-
dataType: DataType.FLOAT32,
|
|
301
|
+
name: id.create(),
|
|
294
302
|
virtual: true,
|
|
303
|
+
dataType: DataType.INT64,
|
|
295
304
|
expression: "return 1",
|
|
296
305
|
});
|
|
297
306
|
|
|
@@ -299,11 +308,12 @@ describe("Channel", () => {
|
|
|
299
308
|
key: channel.key,
|
|
300
309
|
name: channel.name,
|
|
301
310
|
dataType: channel.dataType,
|
|
311
|
+
leaseholder: channel.leaseholder,
|
|
302
312
|
virtual: true,
|
|
303
313
|
expression: "return 2",
|
|
304
314
|
});
|
|
305
315
|
|
|
306
|
-
const channelsWithName = await client.channels.retrieve([
|
|
316
|
+
const channelsWithName = await client.channels.retrieve([channel.name]);
|
|
307
317
|
expect(channelsWithName.length).toEqual(1);
|
|
308
318
|
|
|
309
319
|
expect(updated.expression).toEqual("return 2");
|
|
@@ -314,7 +324,7 @@ describe("Channel", () => {
|
|
|
314
324
|
|
|
315
325
|
test("update calculated channel name", async () => {
|
|
316
326
|
const channel = await client.channels.create({
|
|
317
|
-
name:
|
|
327
|
+
name: id.create(),
|
|
318
328
|
dataType: DataType.FLOAT32,
|
|
319
329
|
virtual: true,
|
|
320
330
|
expression: "return 1",
|
|
@@ -322,35 +332,15 @@ describe("Channel", () => {
|
|
|
322
332
|
|
|
323
333
|
const updated = await client.channels.create({
|
|
324
334
|
key: channel.key,
|
|
325
|
-
name:
|
|
335
|
+
name: id.create(),
|
|
326
336
|
dataType: channel.dataType,
|
|
327
337
|
virtual: true,
|
|
328
338
|
expression: channel.expression,
|
|
329
339
|
});
|
|
330
|
-
expect(updated.name).toEqual(
|
|
331
|
-
|
|
332
|
-
const retrieved = await client.channels.retrieve(channel.key);
|
|
333
|
-
expect(retrieved.name).toEqual("new-name");
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
test("should not allow updates to non-virtual channels", async () => {
|
|
337
|
-
const channel = await client.channels.create({
|
|
338
|
-
name: "regular-channel",
|
|
339
|
-
leaseholder: 1,
|
|
340
|
-
virtual: true,
|
|
341
|
-
dataType: DataType.FLOAT32,
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
const _updated = await client.channels.create({
|
|
345
|
-
key: channel.key,
|
|
346
|
-
name: "new-name",
|
|
347
|
-
leaseholder: channel.leaseholder,
|
|
348
|
-
virtual: true,
|
|
349
|
-
dataType: channel.dataType,
|
|
350
|
-
});
|
|
340
|
+
expect(updated.name).toEqual(updated.name);
|
|
351
341
|
|
|
352
342
|
const retrieved = await client.channels.retrieve(channel.key);
|
|
353
|
-
expect(retrieved.name).toEqual(
|
|
343
|
+
expect(retrieved.name).toEqual(updated.name);
|
|
354
344
|
});
|
|
355
345
|
});
|
|
356
346
|
});
|
package/src/channel/client.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
keyZ,
|
|
27
27
|
type Name,
|
|
28
28
|
type New,
|
|
29
|
+
ontologyID,
|
|
29
30
|
type Operation,
|
|
30
31
|
type Params,
|
|
31
32
|
type Payload,
|
|
@@ -223,8 +224,6 @@ export class Channel {
|
|
|
223
224
|
|
|
224
225
|
export const CALCULATION_STATUS_CHANNEL_NAME = "sy_calculation_status";
|
|
225
226
|
|
|
226
|
-
const RETRIEVE_GROUP_ENDPOINT = "/channel/retrieve-group";
|
|
227
|
-
|
|
228
227
|
const retrieveGroupReqZ = z.object({});
|
|
229
228
|
|
|
230
229
|
const retrieveGroupResZ = z.object({ group: group.groupZ });
|
|
@@ -427,7 +426,7 @@ export class Client {
|
|
|
427
426
|
async retrieveGroup(): Promise<group.Group> {
|
|
428
427
|
const res = await sendRequired(
|
|
429
428
|
this.client,
|
|
430
|
-
|
|
429
|
+
"/channel/retrieve-group",
|
|
431
430
|
{},
|
|
432
431
|
retrieveGroupReqZ,
|
|
433
432
|
retrieveGroupResZ,
|
|
@@ -442,11 +441,6 @@ export const isCalculated = ({ virtual, expression }: Payload): boolean =>
|
|
|
442
441
|
export const isLegacyCalculated = (pld: Payload): boolean =>
|
|
443
442
|
isCalculated(pld) && pld.requires.length > 0;
|
|
444
443
|
|
|
445
|
-
export const ontologyID = (key: Key): ontology.ID => ({
|
|
446
|
-
type: "channel",
|
|
447
|
-
key: key.toString(),
|
|
448
|
-
});
|
|
449
|
-
|
|
450
444
|
export const resolveLegacyCalculatedIndex = async (
|
|
451
445
|
retrieve: (key: Key) => Promise<Payload | null>,
|
|
452
446
|
channel: Payload,
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Copyright 2025 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 { DataType } from "@synnaxlabs/x";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { escapeInvalidName, nameZ, newZ } from "@/channel/payload";
|
|
14
|
+
|
|
15
|
+
describe("nameZ", () => {
|
|
16
|
+
describe("valid names", () => {
|
|
17
|
+
const validNames = [
|
|
18
|
+
["temperature", "lowercase letters"],
|
|
19
|
+
["Pressure", "capitalized word"],
|
|
20
|
+
["sensor1", "letters followed by digit"],
|
|
21
|
+
["sensor_temp", "letters with underscore"],
|
|
22
|
+
["temp123", "letters followed by digits"],
|
|
23
|
+
["Sensor_temp", "capitalized with underscore"],
|
|
24
|
+
["temp123_sensor_temp", "complex valid name"],
|
|
25
|
+
["_private", "underscore prefix"],
|
|
26
|
+
["__double", "double underscore prefix"],
|
|
27
|
+
["a", "single letter"],
|
|
28
|
+
["A", "single capital letter"],
|
|
29
|
+
["_", "single underscore"],
|
|
30
|
+
["_1", "underscore followed by digit"],
|
|
31
|
+
];
|
|
32
|
+
validNames.forEach(([name, description]) => {
|
|
33
|
+
it(`should accept ${name} (${description})`, () => {
|
|
34
|
+
const result = nameZ.safeParse(name);
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("invalid names", () => {
|
|
41
|
+
it("should reject empty string", () => {
|
|
42
|
+
const result = nameZ.safeParse("");
|
|
43
|
+
expect(result.success).toBe(false);
|
|
44
|
+
expect(result.error?.issues[0].message).toContain("Name must not be empty");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should reject name starting with digit", () => {
|
|
48
|
+
const result = nameZ.safeParse("1sensor");
|
|
49
|
+
expect(result.success).toBe(false);
|
|
50
|
+
// Regex validation covers both "cannot start with digit" and "invalid characters"
|
|
51
|
+
expect(result.error?.issues[0].message).toContain(
|
|
52
|
+
"can only contain letters, digits, and underscores",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should reject name with spaces", () => {
|
|
57
|
+
const result = nameZ.safeParse("my channel");
|
|
58
|
+
expect(result.success).toBe(false);
|
|
59
|
+
expect(result.error?.issues[0].message).toContain(
|
|
60
|
+
"can only contain letters, digits, and underscores",
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should reject name with special characters", () => {
|
|
65
|
+
const result = nameZ.safeParse("sensor!");
|
|
66
|
+
expect(result.success).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should reject name with hyphens", () => {
|
|
70
|
+
const result = nameZ.safeParse("sensor-temp");
|
|
71
|
+
expect(result.success).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should reject name with dots", () => {
|
|
75
|
+
const result = nameZ.safeParse("sensor.temp");
|
|
76
|
+
expect(result.success).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should reject name with parentheses", () => {
|
|
80
|
+
const result = nameZ.safeParse("sensor(1)");
|
|
81
|
+
expect(result.success).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should reject name with brackets", () => {
|
|
85
|
+
const result = nameZ.safeParse("sensor[0]");
|
|
86
|
+
expect(result.success).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("newZ", () => {
|
|
92
|
+
const validNewChannel = {
|
|
93
|
+
name: "temperature_sensor",
|
|
94
|
+
dataType: DataType.FLOAT32,
|
|
95
|
+
virtual: true,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
describe("name validation", () => {
|
|
99
|
+
it("should accept valid channel names", () => {
|
|
100
|
+
const result = newZ.safeParse(validNewChannel);
|
|
101
|
+
expect(result.success).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should reject empty name", () => {
|
|
105
|
+
const result = newZ.safeParse({ ...validNewChannel, name: "" });
|
|
106
|
+
expect(result.success).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should reject name starting with digit", () => {
|
|
110
|
+
const result = newZ.safeParse({ ...validNewChannel, name: "1sensor" });
|
|
111
|
+
expect(result.success).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should reject name with spaces", () => {
|
|
115
|
+
const result = newZ.safeParse({ ...validNewChannel, name: "my channel" });
|
|
116
|
+
expect(result.success).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should reject name with special characters", () => {
|
|
120
|
+
const result = newZ.safeParse({ ...validNewChannel, name: "sensor-temp" });
|
|
121
|
+
expect(result.success).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should accept name with underscores", () => {
|
|
125
|
+
const result = newZ.safeParse({
|
|
126
|
+
...validNewChannel,
|
|
127
|
+
name: "sensor_temp_123",
|
|
128
|
+
});
|
|
129
|
+
expect(result.success).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should accept name starting with underscore", () => {
|
|
133
|
+
const result = newZ.safeParse({ ...validNewChannel, name: "_private_sensor" });
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe("escapeInvalidName", () => {
|
|
139
|
+
it("should escape invalid name", () => {
|
|
140
|
+
const result = escapeInvalidName("sensor-temp");
|
|
141
|
+
expect(result).toBe("sensor_temp");
|
|
142
|
+
});
|
|
143
|
+
it("should escape name starting with digit", () => {
|
|
144
|
+
const result = escapeInvalidName("1sensor");
|
|
145
|
+
expect(result).toBe("_1sensor");
|
|
146
|
+
});
|
|
147
|
+
it("should escape name with spaces", () => {
|
|
148
|
+
const result = escapeInvalidName("my channel");
|
|
149
|
+
expect(result).toBe("my_channel");
|
|
150
|
+
});
|
|
151
|
+
it("should escape name with special characters", () => {
|
|
152
|
+
const result = escapeInvalidName("sensor!");
|
|
153
|
+
expect(result).toBe("sensor_");
|
|
154
|
+
});
|
|
155
|
+
it("should escape name with hyphens", () => {
|
|
156
|
+
const result = escapeInvalidName("sensor-temp");
|
|
157
|
+
expect(result).toBe("sensor_temp");
|
|
158
|
+
});
|
|
159
|
+
it("should escape name with dots", () => {
|
|
160
|
+
const result = escapeInvalidName("sensor.temp");
|
|
161
|
+
expect(result).toBe("sensor_temp");
|
|
162
|
+
});
|
|
163
|
+
it("should allow an empty string by default", () => {
|
|
164
|
+
const result = escapeInvalidName("");
|
|
165
|
+
expect(result).toBe("");
|
|
166
|
+
});
|
|
167
|
+
it("should change empty string to underscore when changeEmptyToUnderscore is true", () => {
|
|
168
|
+
const result = escapeInvalidName("", true);
|
|
169
|
+
expect(result).toBe("_");
|
|
170
|
+
});
|
|
171
|
+
});
|