tabletcommand-backend-models 7.4.33 → 7.4.34
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/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/models/audio-stream-authentication.js +52 -0
- package/build/models/audio-stream-authentication.js.map +1 -0
- package/build/models/department.js +23 -1
- package/build/models/department.js.map +1 -1
- package/build/test/audio-stream-authentication.js +74 -0
- package/build/test/audio-stream-authentication.js.map +1 -0
- package/build/test/audio-stream.js +212 -0
- package/build/test/audio-stream.js.map +1 -0
- package/build/test/department.js +1 -0
- package/build/test/department.js.map +1 -1
- package/build/test/mock.js +10 -0
- package/build/test/mock.js.map +1 -1
- package/build/types/audio-stream-authentication.js +3 -0
- package/build/types/audio-stream-authentication.js.map +1 -0
- package/definitions/index.d.ts +6 -0
- package/definitions/index.d.ts.map +1 -1
- package/definitions/models/audio-stream-authentication.d.ts +13 -0
- package/definitions/models/audio-stream-authentication.d.ts.map +1 -0
- package/definitions/models/department.d.ts.map +1 -1
- package/definitions/test/audio-stream-authentication.d.ts +2 -0
- package/definitions/test/audio-stream-authentication.d.ts.map +1 -0
- package/definitions/test/audio-stream.d.ts +2 -0
- package/definitions/test/audio-stream.d.ts.map +1 -0
- package/definitions/test/mock.d.ts +9 -0
- package/definitions/test/mock.d.ts.map +1 -1
- package/definitions/types/audio-stream-authentication.d.ts +9 -0
- package/definitions/types/audio-stream-authentication.d.ts.map +1 -0
- package/definitions/types/department.d.ts +6 -0
- package/definitions/types/department.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/models/audio-stream-authentication.ts +63 -0
- package/src/models/department.ts +25 -1
- package/src/test/audio-stream-authentication.ts +81 -0
- package/src/test/audio-stream.ts +230 -0
- package/src/test/department.ts +1 -0
- package/src/test/mock.ts +11 -0
- package/src/types/audio-stream-authentication.ts +9 -0
- package/src/types/department.ts +7 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
import "mocha";
|
|
3
|
+
|
|
4
|
+
import * as m from "../index";
|
|
5
|
+
import * as config from "./config";
|
|
6
|
+
import mockModule from "./mock";
|
|
7
|
+
|
|
8
|
+
describe("AudioStreamAuthentication", function() {
|
|
9
|
+
let models: m.BackendModels, mongoose: m.MongooseModule;
|
|
10
|
+
let testItem: Partial<m.AudioStreamAuthentication>;
|
|
11
|
+
beforeEach(async function() {
|
|
12
|
+
const c = await m.connect(config.url);
|
|
13
|
+
models = c.models;
|
|
14
|
+
mongoose = c.mongoose;
|
|
15
|
+
const mock = mockModule({
|
|
16
|
+
mongoose
|
|
17
|
+
});
|
|
18
|
+
testItem = mock.audioStreamAuthentication;
|
|
19
|
+
await mock.cleanup();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(async function() {
|
|
23
|
+
await mongoose.disconnect();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("is saved", async function() {
|
|
27
|
+
assert.isObject(testItem);
|
|
28
|
+
const item = new models.AudioStreamAuthentication(testItem);
|
|
29
|
+
const sut = await item.save();
|
|
30
|
+
assert.isNotNull(sut._id);
|
|
31
|
+
assert.isNotNull(sut.id);
|
|
32
|
+
assert.equal(sut._id.toString(), testItem._id?.toString());
|
|
33
|
+
|
|
34
|
+
assert.equal(sut.name, "test-stream");
|
|
35
|
+
assert.equal(sut.rotateInterval, 3600);
|
|
36
|
+
|
|
37
|
+
assert.isObject(sut.secret);
|
|
38
|
+
assert.equal(sut.secret?.iv, testItem.secret?.iv);
|
|
39
|
+
assert.equal(sut.secret?.encryptedData, testItem.secret?.encryptedData);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("enforces unique name constraint", async function() {
|
|
43
|
+
const item1 = new models.AudioStreamAuthentication(testItem);
|
|
44
|
+
await item1.save();
|
|
45
|
+
|
|
46
|
+
const item2 = new models.AudioStreamAuthentication({
|
|
47
|
+
name: "test-stream",
|
|
48
|
+
secret: {
|
|
49
|
+
iv: "different-iv",
|
|
50
|
+
encryptedData: "different-data"
|
|
51
|
+
},
|
|
52
|
+
rotateInterval: 7200,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await item2.save();
|
|
57
|
+
assert.fail("Should have thrown duplicate key error");
|
|
58
|
+
} catch (error) {
|
|
59
|
+
assert.include(String((error as Error).message), "duplicate key");
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("allows different names", async function() {
|
|
64
|
+
const item1 = new models.AudioStreamAuthentication(testItem);
|
|
65
|
+
await item1.save();
|
|
66
|
+
|
|
67
|
+
const item2 = new models.AudioStreamAuthentication({
|
|
68
|
+
name: "different-stream",
|
|
69
|
+
secret: {
|
|
70
|
+
iv: "different-iv",
|
|
71
|
+
encryptedData: "different-data"
|
|
72
|
+
},
|
|
73
|
+
rotateInterval: 7200,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const sut = await item2.save();
|
|
77
|
+
assert.isNotNull(sut._id);
|
|
78
|
+
assert.equal(sut.name, "different-stream");
|
|
79
|
+
assert.equal(sut.rotateInterval, 7200);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
import "mocha";
|
|
3
|
+
|
|
4
|
+
import * as m from "../index";
|
|
5
|
+
import * as config from "./config";
|
|
6
|
+
import mockModule from "./mock";
|
|
7
|
+
|
|
8
|
+
describe("AudioStream", function() {
|
|
9
|
+
let models: m.BackendModels, mongoose: m.MongooseModule;
|
|
10
|
+
let authRecord: m.AudioStreamAuthentication;
|
|
11
|
+
|
|
12
|
+
beforeEach(async function() {
|
|
13
|
+
const c = await m.connect(config.url);
|
|
14
|
+
models = c.models;
|
|
15
|
+
mongoose = c.mongoose;
|
|
16
|
+
const mock = mockModule({
|
|
17
|
+
mongoose
|
|
18
|
+
});
|
|
19
|
+
await mock.cleanup();
|
|
20
|
+
|
|
21
|
+
// Create an AudioStreamAuthentication record to reference
|
|
22
|
+
const authItem = new models.AudioStreamAuthentication({
|
|
23
|
+
name: "test-auth-record",
|
|
24
|
+
secret: {
|
|
25
|
+
iv: "test-iv",
|
|
26
|
+
encryptedData: "test-encrypted-data"
|
|
27
|
+
},
|
|
28
|
+
rotateInterval: 3600,
|
|
29
|
+
});
|
|
30
|
+
authRecord = await authItem.save();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async function() {
|
|
34
|
+
await mongoose.disconnect();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("saves Department with AudioStream authentication", async function() {
|
|
38
|
+
const department = new models.Department({
|
|
39
|
+
department: "Test Fire Department",
|
|
40
|
+
apikey: "test-api-key-123",
|
|
41
|
+
audioConfiguration: [
|
|
42
|
+
{
|
|
43
|
+
group: "Fire Dispatch",
|
|
44
|
+
order: 1,
|
|
45
|
+
streams: [
|
|
46
|
+
{
|
|
47
|
+
description: "Main Dispatch Channel",
|
|
48
|
+
channel: "CH1",
|
|
49
|
+
url: "https://example.com/stream1",
|
|
50
|
+
order: 1,
|
|
51
|
+
authentication: {
|
|
52
|
+
authId: authRecord._id,
|
|
53
|
+
app: "test-app",
|
|
54
|
+
streamKey: "stream-key-123",
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const saved = await department.save();
|
|
63
|
+
|
|
64
|
+
assert.isNotNull(saved._id);
|
|
65
|
+
assert.isArray(saved.audioConfiguration);
|
|
66
|
+
assert.equal(saved.audioConfiguration.length, 1);
|
|
67
|
+
|
|
68
|
+
const group = saved.audioConfiguration[0];
|
|
69
|
+
if (!group) {
|
|
70
|
+
assert.fail("Group should be defined");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
assert.equal(group.group, "Fire Dispatch");
|
|
74
|
+
assert.equal(group.order, 1);
|
|
75
|
+
assert.equal(group.streams.length, 1);
|
|
76
|
+
|
|
77
|
+
const stream = group.streams[0];
|
|
78
|
+
if (!stream) {
|
|
79
|
+
assert.fail("Stream should be defined");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
assert.equal(stream.description, "Main Dispatch Channel");
|
|
83
|
+
assert.equal(stream.channel, "CH1");
|
|
84
|
+
assert.equal(stream.url, "https://example.com/stream1");
|
|
85
|
+
assert.equal(stream.order, 1);
|
|
86
|
+
|
|
87
|
+
// Verify authentication
|
|
88
|
+
assert.isObject(stream.authentication);
|
|
89
|
+
if (!stream.authentication) {
|
|
90
|
+
assert.fail("Authentication should be defined");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
assert.equal(stream.authentication.authId.toString(), authRecord._id.toString());
|
|
94
|
+
assert.equal(stream.authentication.app, "test-app");
|
|
95
|
+
assert.equal(stream.authentication.streamKey, "stream-key-123");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("saves Department with AudioStream without authentication", async function() {
|
|
99
|
+
const department = new models.Department({
|
|
100
|
+
department: "Test Fire Department 2",
|
|
101
|
+
apikey: "test-api-key-456",
|
|
102
|
+
audioConfiguration: [
|
|
103
|
+
{
|
|
104
|
+
group: "Police Dispatch",
|
|
105
|
+
order: 1,
|
|
106
|
+
streams: [
|
|
107
|
+
{
|
|
108
|
+
description: "Public Channel",
|
|
109
|
+
channel: "CH2",
|
|
110
|
+
url: "https://example.com/stream2",
|
|
111
|
+
order: 1,
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const saved = await department.save();
|
|
119
|
+
|
|
120
|
+
assert.isNotNull(saved._id);
|
|
121
|
+
assert.isArray(saved.audioConfiguration);
|
|
122
|
+
assert.equal(saved.audioConfiguration.length, 1);
|
|
123
|
+
|
|
124
|
+
const group = saved.audioConfiguration[0];
|
|
125
|
+
if (!group) {
|
|
126
|
+
assert.fail("Group should be defined");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const stream = group.streams[0];
|
|
130
|
+
if (!stream) {
|
|
131
|
+
assert.fail("Stream should be defined");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
assert.equal(stream.description, "Public Channel");
|
|
135
|
+
assert.isUndefined(stream.authentication);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("saves Department with mixed AudioStreams (with and without auth)", async function() {
|
|
139
|
+
const department = new models.Department({
|
|
140
|
+
department: "Test Mixed Department",
|
|
141
|
+
apikey: "test-api-key-789",
|
|
142
|
+
audioConfiguration: [
|
|
143
|
+
{
|
|
144
|
+
group: "Dispatch Channels",
|
|
145
|
+
order: 1,
|
|
146
|
+
streams: [
|
|
147
|
+
{
|
|
148
|
+
description: "Secure Channel",
|
|
149
|
+
channel: "CH1",
|
|
150
|
+
url: "https://example.com/secure",
|
|
151
|
+
order: 1,
|
|
152
|
+
authentication: {
|
|
153
|
+
authId: authRecord._id,
|
|
154
|
+
app: "secure-app",
|
|
155
|
+
streamKey: "secure-key-456",
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
description: "Public Channel",
|
|
160
|
+
channel: "CH2",
|
|
161
|
+
url: "https://example.com/public",
|
|
162
|
+
order: 2,
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const saved = await department.save();
|
|
170
|
+
|
|
171
|
+
const group = saved.audioConfiguration[0];
|
|
172
|
+
if (!group) {
|
|
173
|
+
assert.fail("Group should be defined");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
assert.equal(group.streams.length, 2);
|
|
177
|
+
|
|
178
|
+
const secureStream = group.streams[0];
|
|
179
|
+
if (!secureStream) {
|
|
180
|
+
assert.fail("Secure stream should be defined");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
assert.isObject(secureStream.authentication);
|
|
184
|
+
if (!secureStream.authentication) {
|
|
185
|
+
assert.fail("Authentication should be defined");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
assert.equal(secureStream.authentication.app, "secure-app");
|
|
189
|
+
|
|
190
|
+
const publicStream = group.streams[1];
|
|
191
|
+
if (!publicStream) {
|
|
192
|
+
assert.fail("Public stream should be defined");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
assert.isUndefined(publicStream.authentication);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("validates required authentication fields when present", async function() {
|
|
199
|
+
const department = new models.Department({
|
|
200
|
+
department: "Test Invalid Department",
|
|
201
|
+
apikey: "test-api-key-invalid",
|
|
202
|
+
audioConfiguration: [
|
|
203
|
+
{
|
|
204
|
+
group: "Test Group",
|
|
205
|
+
order: 1,
|
|
206
|
+
streams: [
|
|
207
|
+
{
|
|
208
|
+
description: "Invalid Stream",
|
|
209
|
+
channel: "CH1",
|
|
210
|
+
url: "https://example.com/invalid",
|
|
211
|
+
order: 1,
|
|
212
|
+
authentication: {
|
|
213
|
+
authId: authRecord._id,
|
|
214
|
+
app: "test-app",
|
|
215
|
+
// Missing streamKey - should fail validation
|
|
216
|
+
} as unknown as { authId: m.AudioStreamAuthentication["_id"]; app: string; streamKey: string; }
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await department.save();
|
|
225
|
+
assert.fail("Should have thrown validation error");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
assert.include(String((error as Error).message), "streamKey");
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
package/src/test/department.ts
CHANGED
package/src/test/mock.ts
CHANGED
|
@@ -939,6 +939,16 @@ export default function mockModule(dependencies: { mongoose: Mongoose; }) {
|
|
|
939
939
|
},
|
|
940
940
|
};
|
|
941
941
|
|
|
942
|
+
const audioStreamAuthentication = {
|
|
943
|
+
_id: new mongoose.Types.ObjectId(),
|
|
944
|
+
name: "test-stream",
|
|
945
|
+
secret: {
|
|
946
|
+
iv: "a1b2c3d4e5f6",
|
|
947
|
+
encryptedData: "encrypted-secret-data-here"
|
|
948
|
+
},
|
|
949
|
+
rotateInterval: 3600,
|
|
950
|
+
};
|
|
951
|
+
|
|
942
952
|
const gstMapping = {
|
|
943
953
|
_id: new mongoose.Types.ObjectId(),
|
|
944
954
|
departmentId: "d123",
|
|
@@ -1475,6 +1485,7 @@ export default function mockModule(dependencies: { mongoose: Mongoose; }) {
|
|
|
1475
1485
|
agency,
|
|
1476
1486
|
arcGISGroup,
|
|
1477
1487
|
assignment,
|
|
1488
|
+
audioStreamAuthentication,
|
|
1478
1489
|
battalion,
|
|
1479
1490
|
cadIncident,
|
|
1480
1491
|
cadIncidentBlock,
|
package/src/types/department.ts
CHANGED
|
@@ -243,11 +243,18 @@ export interface WebDisclaimerType {
|
|
|
243
243
|
enabled: boolean,
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
export interface AudioStreamAuthType {
|
|
247
|
+
authId: Types.ObjectId,
|
|
248
|
+
app: string,
|
|
249
|
+
streamKey: string,
|
|
250
|
+
}
|
|
251
|
+
|
|
246
252
|
export interface AudioStreamType {
|
|
247
253
|
description: string,
|
|
248
254
|
channel: string,
|
|
249
255
|
url: string,
|
|
250
256
|
order: number,
|
|
257
|
+
authentication?: AudioStreamAuthType,
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
export interface AudioStreamGroupType {
|