@kusinta/iot-schema 0.1.0-beta.6 → 0.1.0-beta.7
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/package.json +1 -1
- package/src/__tests__/access.test.js +0 -134
- package/src/__tests__/common.test.js +0 -29
- package/src/__tests__/connector.test.js +0 -144
- package/src/__tests__/device.test.js +0 -194
- package/src/__tests__/identity.test.js +0 -43
- package/src/__tests__/space.test.js +0 -54
- package/src/__tests__/vendor.test.js +0 -60
- package/src/__tests__/webrtc.test.js +0 -268
package/package.json
CHANGED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import { Role, PermissionAction } from '../kusinta/iot/access/v1/roles_pb.js'
|
|
5
|
-
import {
|
|
6
|
-
PropertyConstraintSchema,
|
|
7
|
-
DeviceAclSchema,
|
|
8
|
-
EffectivePermissionsSchema,
|
|
9
|
-
} from '../kusinta/iot/access/v1/acl_pb.js'
|
|
10
|
-
|
|
11
|
-
describe('Role and PermissionAction enums', () => {
|
|
12
|
-
it('Role.RESIDENT is defined', () => {
|
|
13
|
-
expect(Role.RESIDENT).toBeDefined()
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('Role.PROPERTY_OWNER is defined', () => {
|
|
17
|
-
expect(Role.PROPERTY_OWNER).toBeDefined()
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('PermissionAction.WRITE is defined', () => {
|
|
21
|
-
expect(PermissionAction.WRITE).toBeDefined()
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
describe('PropertyConstraint — property-owner setpoint limits', () => {
|
|
26
|
-
it('round-trips int_max constraint for MaxHeatSetpointLimit', () => {
|
|
27
|
-
const c = create(PropertyConstraintSchema, {
|
|
28
|
-
attributeName: 'MaxHeatSetpointLimit',
|
|
29
|
-
constraint: { case: 'intMax', value: 2200 },
|
|
30
|
-
clusterIdHex: '0201',
|
|
31
|
-
})
|
|
32
|
-
const decoded = fromBinary(PropertyConstraintSchema, toBinary(PropertyConstraintSchema, c))
|
|
33
|
-
expect(decoded.attributeName).toBe('MaxHeatSetpointLimit')
|
|
34
|
-
expect(decoded.constraint?.case).toBe('intMax')
|
|
35
|
-
if (decoded.constraint?.case === 'intMax') {
|
|
36
|
-
expect(decoded.constraint.value).toBe(2200)
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('round-trips int_min constraint for MinHeatSetpointLimit', () => {
|
|
41
|
-
const c = create(PropertyConstraintSchema, {
|
|
42
|
-
attributeName: 'MinHeatSetpointLimit',
|
|
43
|
-
constraint: { case: 'intMin', value: 1600 },
|
|
44
|
-
clusterIdHex: '0201',
|
|
45
|
-
})
|
|
46
|
-
const decoded = fromBinary(PropertyConstraintSchema, toBinary(PropertyConstraintSchema, c))
|
|
47
|
-
expect(decoded.constraint?.case).toBe('intMin')
|
|
48
|
-
if (decoded.constraint?.case === 'intMin') {
|
|
49
|
-
expect(decoded.constraint.value).toBe(1600)
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('round-trips uint_max constraint', () => {
|
|
54
|
-
const c = create(PropertyConstraintSchema, {
|
|
55
|
-
attributeName: 'CurrentLevel',
|
|
56
|
-
constraint: { case: 'uintMax', value: 200 },
|
|
57
|
-
clusterIdHex: '0008',
|
|
58
|
-
})
|
|
59
|
-
const decoded = fromBinary(PropertyConstraintSchema, toBinary(PropertyConstraintSchema, c))
|
|
60
|
-
expect(decoded.constraint?.case).toBe('uintMax')
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
describe('DeviceAcl', () => {
|
|
65
|
-
it('round-trips resident acl with allowed actions and constraints', () => {
|
|
66
|
-
const acl = create(DeviceAclSchema, {
|
|
67
|
-
deviceId: { value: 'therm-1' },
|
|
68
|
-
userId: { value: 'user-resident-1' },
|
|
69
|
-
role: Role.RESIDENT,
|
|
70
|
-
allowedActions: [PermissionAction.READ, PermissionAction.WRITE],
|
|
71
|
-
allowedAttributes: ['OccupiedHeatingSetpoint', 'LocalTemperature'],
|
|
72
|
-
propertyConstraints: [
|
|
73
|
-
{
|
|
74
|
-
attributeName: 'OccupiedHeatingSetpoint',
|
|
75
|
-
constraint: { case: 'intMax', value: 2200 },
|
|
76
|
-
clusterIdHex: '0201',
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
attributeName: 'OccupiedHeatingSetpoint',
|
|
80
|
-
constraint: { case: 'intMin', value: 1600 },
|
|
81
|
-
clusterIdHex: '0201',
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
})
|
|
85
|
-
const decoded = fromBinary(DeviceAclSchema, toBinary(DeviceAclSchema, acl))
|
|
86
|
-
expect(decoded.deviceId?.value).toBe('therm-1')
|
|
87
|
-
expect(decoded.userId?.value).toBe('user-resident-1')
|
|
88
|
-
expect(decoded.role).toBe(Role.RESIDENT)
|
|
89
|
-
expect(decoded.allowedActions).toEqual([PermissionAction.READ, PermissionAction.WRITE])
|
|
90
|
-
expect(decoded.allowedAttributes).toEqual(['OccupiedHeatingSetpoint', 'LocalTemperature'])
|
|
91
|
-
expect(decoded.propertyConstraints).toHaveLength(2)
|
|
92
|
-
expect(decoded.propertyConstraints[0].constraint?.case).toBe('intMax')
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('round-trips property-owner acl', () => {
|
|
96
|
-
const acl = create(DeviceAclSchema, {
|
|
97
|
-
deviceId: { value: 'therm-1' },
|
|
98
|
-
userId: { value: 'user-owner-1' },
|
|
99
|
-
role: Role.PROPERTY_OWNER,
|
|
100
|
-
allowedActions: [PermissionAction.READ, PermissionAction.WRITE, PermissionAction.OBSERVE],
|
|
101
|
-
})
|
|
102
|
-
const decoded = fromBinary(DeviceAclSchema, toBinary(DeviceAclSchema, acl))
|
|
103
|
-
expect(decoded.role).toBe(Role.PROPERTY_OWNER)
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
describe('EffectivePermissions', () => {
|
|
108
|
-
it('round-trips with multiple device acls', () => {
|
|
109
|
-
const perms = create(EffectivePermissionsSchema, {
|
|
110
|
-
userId: { value: 'user-1' },
|
|
111
|
-
gatewayId: { value: 'gw-1' },
|
|
112
|
-
deviceAcls: [
|
|
113
|
-
{
|
|
114
|
-
deviceId: { value: 'therm-1' },
|
|
115
|
-
userId: { value: 'user-1' },
|
|
116
|
-
role: Role.RESIDENT,
|
|
117
|
-
allowedActions: [PermissionAction.READ, PermissionAction.WRITE],
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
deviceId: { value: 'light-1' },
|
|
121
|
-
userId: { value: 'user-1' },
|
|
122
|
-
role: Role.RESIDENT,
|
|
123
|
-
allowedActions: [PermissionAction.READ, PermissionAction.WRITE],
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
})
|
|
127
|
-
const decoded = fromBinary(EffectivePermissionsSchema, toBinary(EffectivePermissionsSchema, perms))
|
|
128
|
-
expect(decoded.userId?.value).toBe('user-1')
|
|
129
|
-
expect(decoded.gatewayId?.value).toBe('gw-1')
|
|
130
|
-
expect(decoded.deviceAcls).toHaveLength(2)
|
|
131
|
-
expect(decoded.deviceAcls[0].deviceId?.value).toBe('therm-1')
|
|
132
|
-
expect(decoded.deviceAcls[1].deviceId?.value).toBe('light-1')
|
|
133
|
-
})
|
|
134
|
-
})
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import { ErrorDetailSchema } from '../kusinta/iot/common/v1/types_pb.js'
|
|
5
|
-
|
|
6
|
-
describe('ErrorDetail', () => {
|
|
7
|
-
it('round-trips code and message', () => {
|
|
8
|
-
const original = create(ErrorDetailSchema, { code: 'NOT_FOUND', message: 'device not found', metadata: {} })
|
|
9
|
-
const decoded = fromBinary(ErrorDetailSchema, toBinary(ErrorDetailSchema, original))
|
|
10
|
-
expect(decoded.code).toBe('NOT_FOUND')
|
|
11
|
-
expect(decoded.message).toBe('device not found')
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('round-trips metadata map entries', () => {
|
|
15
|
-
const original = create(ErrorDetailSchema, {
|
|
16
|
-
code: 'PERMISSION_DENIED',
|
|
17
|
-
message: 'write not allowed',
|
|
18
|
-
metadata: { deviceId: 'abc-123', attribute: 'OccupiedHeatingSetpoint' },
|
|
19
|
-
})
|
|
20
|
-
const decoded = fromBinary(ErrorDetailSchema, toBinary(ErrorDetailSchema, original))
|
|
21
|
-
expect(decoded.metadata['deviceId']).toBe('abc-123')
|
|
22
|
-
expect(decoded.metadata['attribute']).toBe('OccupiedHeatingSetpoint')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('produces an empty byte array for a default instance', () => {
|
|
26
|
-
const bytes = toBinary(ErrorDetailSchema, create(ErrorDetailSchema))
|
|
27
|
-
expect(bytes.length).toBe(0)
|
|
28
|
-
})
|
|
29
|
-
})
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import {
|
|
5
|
-
ConnectRequestSchema,
|
|
6
|
-
ConnectResponseSchema,
|
|
7
|
-
ConnectorHandshakeSchema,
|
|
8
|
-
HandshakeAckSchema,
|
|
9
|
-
DeviceAnnouncementSchema,
|
|
10
|
-
DeviceRemovalSchema,
|
|
11
|
-
} from '../kusinta/iot/connector/v1/connector_pb.js'
|
|
12
|
-
import { ConnectorTransport } from '../kusinta/iot/common/v1/types_pb.js'
|
|
13
|
-
|
|
14
|
-
describe('ConnectorHandshake', () => {
|
|
15
|
-
it('round-trips with known_devices', () => {
|
|
16
|
-
const h = create(ConnectorHandshakeSchema, {
|
|
17
|
-
info: {
|
|
18
|
-
connectorId: { value: 'homematic-ccu3' },
|
|
19
|
-
displayName: 'HomeMatic CCU3',
|
|
20
|
-
version: '1.0.0',
|
|
21
|
-
transport: ConnectorTransport.UNIX_SOCKET,
|
|
22
|
-
endpoint: '/run/kusinta/connectors/homematic.sock',
|
|
23
|
-
supportedDeviceTypeIds: [0x0301, 0x0302],
|
|
24
|
-
},
|
|
25
|
-
knownDevices: [{ deviceId: { value: 'dev-1' }, matterDeviceTypeId: 0x0301 }],
|
|
26
|
-
})
|
|
27
|
-
const decoded = fromBinary(ConnectorHandshakeSchema, toBinary(ConnectorHandshakeSchema, h))
|
|
28
|
-
expect(decoded.info?.connectorId?.value).toBe('homematic-ccu3')
|
|
29
|
-
expect(decoded.info?.displayName).toBe('HomeMatic CCU3')
|
|
30
|
-
expect(decoded.info?.transport).toBe(ConnectorTransport.UNIX_SOCKET)
|
|
31
|
-
expect(decoded.info?.supportedDeviceTypeIds).toEqual([0x0301, 0x0302])
|
|
32
|
-
expect(decoded.knownDevices).toHaveLength(1)
|
|
33
|
-
expect(decoded.knownDevices[0].deviceId?.value).toBe('dev-1')
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('HandshakeAck', () => {
|
|
38
|
-
it('round-trips accepted ack', () => {
|
|
39
|
-
const ack = create(HandshakeAckSchema, { accepted: true, reason: '', gatewayId: { value: 'gw-abc' } })
|
|
40
|
-
const decoded = fromBinary(HandshakeAckSchema, toBinary(HandshakeAckSchema, ack))
|
|
41
|
-
expect(decoded.accepted).toBe(true)
|
|
42
|
-
expect(decoded.gatewayId?.value).toBe('gw-abc')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('round-trips rejected ack with reason', () => {
|
|
46
|
-
const ack = create(HandshakeAckSchema, { accepted: false, reason: 'Unknown connector ID' })
|
|
47
|
-
const decoded = fromBinary(HandshakeAckSchema, toBinary(HandshakeAckSchema, ack))
|
|
48
|
-
expect(decoded.accepted).toBe(false)
|
|
49
|
-
expect(decoded.reason).toBe('Unknown connector ID')
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
describe('DeviceAnnouncement', () => {
|
|
54
|
-
it('round-trips descriptor', () => {
|
|
55
|
-
const ann = create(DeviceAnnouncementSchema, {
|
|
56
|
-
descriptor: { deviceId: { value: 'dev-new-1' }, matterDeviceTypeId: 0x0307, vendorName: 'eQ-3', productName: 'HmIP-STH' },
|
|
57
|
-
})
|
|
58
|
-
const decoded = fromBinary(DeviceAnnouncementSchema, toBinary(DeviceAnnouncementSchema, ann))
|
|
59
|
-
expect(decoded.descriptor?.deviceId?.value).toBe('dev-new-1')
|
|
60
|
-
expect(decoded.descriptor?.matterDeviceTypeId).toBe(0x0307)
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
describe('DeviceRemoval', () => {
|
|
65
|
-
it('round-trips device_id and reason', () => {
|
|
66
|
-
const rem = create(DeviceRemovalSchema, { deviceId: { value: 'dev-1' }, reason: 'Device unpaired from CCU3' })
|
|
67
|
-
const decoded = fromBinary(DeviceRemovalSchema, toBinary(DeviceRemovalSchema, rem))
|
|
68
|
-
expect(decoded.deviceId?.value).toBe('dev-1')
|
|
69
|
-
expect(decoded.reason).toBe('Device unpaired from CCU3')
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe('ConnectRequest oneof payload', () => {
|
|
74
|
-
it('round-trips handshake payload', () => {
|
|
75
|
-
const msg = create(ConnectRequestSchema, {
|
|
76
|
-
messageId: 'msg-001',
|
|
77
|
-
payload: {
|
|
78
|
-
case: 'handshake',
|
|
79
|
-
value: {
|
|
80
|
-
info: {
|
|
81
|
-
connectorId: { value: 'conn-1' },
|
|
82
|
-
displayName: 'Test Connector',
|
|
83
|
-
version: '0.1.0',
|
|
84
|
-
transport: ConnectorTransport.UNIX_SOCKET,
|
|
85
|
-
endpoint: '/run/test.sock',
|
|
86
|
-
},
|
|
87
|
-
knownDevices: [],
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
})
|
|
91
|
-
const decoded = fromBinary(ConnectRequestSchema, toBinary(ConnectRequestSchema, msg))
|
|
92
|
-
expect(decoded.messageId).toBe('msg-001')
|
|
93
|
-
expect(decoded.payload?.case).toBe('handshake')
|
|
94
|
-
if (decoded.payload?.case === 'handshake') {
|
|
95
|
-
expect(decoded.payload.value.info?.connectorId?.value).toBe('conn-1')
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('round-trips property_update payload', () => {
|
|
100
|
-
const msg = create(ConnectRequestSchema, {
|
|
101
|
-
messageId: 'msg-002',
|
|
102
|
-
payload: {
|
|
103
|
-
case: 'propertyUpdate',
|
|
104
|
-
value: {
|
|
105
|
-
updates: [{
|
|
106
|
-
deviceId: { value: 'dev-1' },
|
|
107
|
-
attributeName: 'OccupiedHeatingSetpoint',
|
|
108
|
-
value: { case: 'intValue', value: 2150 },
|
|
109
|
-
clusterIdHex: '0201',
|
|
110
|
-
}],
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
})
|
|
114
|
-
const decoded = fromBinary(ConnectRequestSchema, toBinary(ConnectRequestSchema, msg))
|
|
115
|
-
expect(decoded.payload?.case).toBe('propertyUpdate')
|
|
116
|
-
if (decoded.payload?.case === 'propertyUpdate') {
|
|
117
|
-
expect(decoded.payload.value.updates).toHaveLength(1)
|
|
118
|
-
expect(decoded.payload.value.updates[0].attributeName).toBe('OccupiedHeatingSetpoint')
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('round-trips heartbeat payload', () => {
|
|
123
|
-
const msg = create(ConnectRequestSchema, {
|
|
124
|
-
messageId: 'msg-hb-1',
|
|
125
|
-
payload: { case: 'heartbeat', value: {} },
|
|
126
|
-
})
|
|
127
|
-
const decoded = fromBinary(ConnectRequestSchema, toBinary(ConnectRequestSchema, msg))
|
|
128
|
-
expect(decoded.payload?.case).toBe('heartbeat')
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
describe('ConnectResponse oneof payload', () => {
|
|
133
|
-
it('round-trips handshake_ack payload', () => {
|
|
134
|
-
const msg = create(ConnectResponseSchema, {
|
|
135
|
-
messageId: 'gw-msg-001',
|
|
136
|
-
payload: { case: 'handshakeAck', value: { accepted: true, gatewayId: { value: 'gw-1' } } },
|
|
137
|
-
})
|
|
138
|
-
const decoded = fromBinary(ConnectResponseSchema, toBinary(ConnectResponseSchema, msg))
|
|
139
|
-
expect(decoded.payload?.case).toBe('handshakeAck')
|
|
140
|
-
if (decoded.payload?.case === 'handshakeAck') {
|
|
141
|
-
expect(decoded.payload.value.accepted).toBe(true)
|
|
142
|
-
}
|
|
143
|
-
})
|
|
144
|
-
})
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import { DeviceDescriptorSchema } from '../kusinta/iot/device/v1/descriptor_pb.js'
|
|
5
|
-
import { DeviceOwnershipType, DeviceLifecycleState } from '../kusinta/iot/common/v1/types_pb.js'
|
|
6
|
-
import {
|
|
7
|
-
ThermostatPropertiesSchema,
|
|
8
|
-
TemperatureSensorPropertiesSchema,
|
|
9
|
-
WindowCoveringPropertiesSchema,
|
|
10
|
-
DoorLockPropertiesSchema,
|
|
11
|
-
ContactSensorPropertiesSchema,
|
|
12
|
-
OnOffLightPropertiesSchema,
|
|
13
|
-
DimmableLightPropertiesSchema,
|
|
14
|
-
} from '../kusinta/iot/device/v1/properties_pb.js'
|
|
15
|
-
import { DeviceSchema } from '../kusinta/iot/device/v1/device_pb.js'
|
|
16
|
-
import { PropertyUpdateSchema, PropertyUpdateBatchSchema } from '../kusinta/iot/device/v1/property_update_pb.js'
|
|
17
|
-
|
|
18
|
-
describe('DeviceDescriptor', () => {
|
|
19
|
-
it('round-trips matter_device_type_id for thermostat (0x0301 = 769)', () => {
|
|
20
|
-
const d = create(DeviceDescriptorSchema, {
|
|
21
|
-
deviceId: { value: 'dev-001' },
|
|
22
|
-
matterDeviceTypeId: 0x0301,
|
|
23
|
-
vendorName: 'eQ-3',
|
|
24
|
-
productName: 'HM-CC-RT-DN',
|
|
25
|
-
serialNumber: 'MEQ1234567',
|
|
26
|
-
nodeLabel: 'Living room thermostat',
|
|
27
|
-
vendorId: 0x0135,
|
|
28
|
-
productId: 0x0095,
|
|
29
|
-
hardwareVersionString: '1.4',
|
|
30
|
-
softwareVersionString: '2.8.0',
|
|
31
|
-
connectorId: { value: 'homematic-ccu3' },
|
|
32
|
-
spaceId: { value: 'room-uuid' },
|
|
33
|
-
ownership: DeviceOwnershipType.COMPANY,
|
|
34
|
-
lifecycle: DeviceLifecycleState.OWNED,
|
|
35
|
-
})
|
|
36
|
-
const decoded = fromBinary(DeviceDescriptorSchema, toBinary(DeviceDescriptorSchema, d))
|
|
37
|
-
expect(decoded.matterDeviceTypeId).toBe(0x0301)
|
|
38
|
-
expect(decoded.vendorName).toBe('eQ-3')
|
|
39
|
-
expect(decoded.ownership).toBe(DeviceOwnershipType.COMPANY)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('round-trips resident-owned lifecycle', () => {
|
|
43
|
-
const d = create(DeviceDescriptorSchema, {
|
|
44
|
-
deviceId: { value: 'dev-resident-001' },
|
|
45
|
-
matterDeviceTypeId: 0x0100,
|
|
46
|
-
ownership: DeviceOwnershipType.RESIDENT,
|
|
47
|
-
lifecycle: DeviceLifecycleState.PENDING_CLAIM,
|
|
48
|
-
ownerUserId: { value: 'user-abc' },
|
|
49
|
-
})
|
|
50
|
-
const decoded = fromBinary(DeviceDescriptorSchema, toBinary(DeviceDescriptorSchema, d))
|
|
51
|
-
expect(decoded.ownership).toBe(DeviceOwnershipType.RESIDENT)
|
|
52
|
-
expect(decoded.lifecycle).toBe(DeviceLifecycleState.PENDING_CLAIM)
|
|
53
|
-
expect(decoded.ownerUserId?.value).toBe('user-abc')
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
describe('ThermostatProperties — Matter Thermostat cluster 0x0201', () => {
|
|
58
|
-
it('round-trips occupied_heating_setpoint (21.50°C = 2150 centidegrees)', () => {
|
|
59
|
-
const p = create(ThermostatPropertiesSchema, { occupiedHeatingSetpoint: 2150 })
|
|
60
|
-
const decoded = fromBinary(ThermostatPropertiesSchema, toBinary(ThermostatPropertiesSchema, p))
|
|
61
|
-
expect(decoded.occupiedHeatingSetpoint).toBe(2150)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('preserves negative local_temperature (frost: -5.00°C = -500)', () => {
|
|
65
|
-
const p = create(ThermostatPropertiesSchema, { localTemperature: -500 })
|
|
66
|
-
const decoded = fromBinary(ThermostatPropertiesSchema, toBinary(ThermostatPropertiesSchema, p))
|
|
67
|
-
expect(decoded.localTemperature).toBe(-500)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('round-trips min/max setpoint limits (property-owner constraints)', () => {
|
|
71
|
-
const p = create(ThermostatPropertiesSchema, { minHeatSetpointLimit: 1600, maxHeatSetpointLimit: 2200 })
|
|
72
|
-
const decoded = fromBinary(ThermostatPropertiesSchema, toBinary(ThermostatPropertiesSchema, p))
|
|
73
|
-
expect(decoded.minHeatSetpointLimit).toBe(1600)
|
|
74
|
-
expect(decoded.maxHeatSetpointLimit).toBe(2200)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('round-trips system_mode enum values', () => {
|
|
78
|
-
const p = create(ThermostatPropertiesSchema, { systemMode: 4 }) // Heat=4
|
|
79
|
-
const decoded = fromBinary(ThermostatPropertiesSchema, toBinary(ThermostatPropertiesSchema, p))
|
|
80
|
-
expect(decoded.systemMode).toBe(4)
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe('TemperatureSensorProperties — Matter Temperature Measurement 0x0402', () => {
|
|
85
|
-
it('round-trips measured_value', () => {
|
|
86
|
-
const p = create(TemperatureSensorPropertiesSchema, { measuredValue: 2100 })
|
|
87
|
-
const decoded = fromBinary(TemperatureSensorPropertiesSchema, toBinary(TemperatureSensorPropertiesSchema, p))
|
|
88
|
-
expect(decoded.measuredValue).toBe(2100)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
describe('WindowCoveringProperties — Matter Window Covering 0x0102', () => {
|
|
93
|
-
it('round-trips position as percent100ths (50% open = 5000)', () => {
|
|
94
|
-
const p = create(WindowCoveringPropertiesSchema, { currentPositionLiftPercent100ths: 5000 })
|
|
95
|
-
const decoded = fromBinary(WindowCoveringPropertiesSchema, toBinary(WindowCoveringPropertiesSchema, p))
|
|
96
|
-
expect(decoded.currentPositionLiftPercent100ths).toBe(5000)
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('DoorLockProperties — Matter Door Lock 0x0101', () => {
|
|
101
|
-
it('round-trips lock_state (Locked=1)', () => {
|
|
102
|
-
const p = create(DoorLockPropertiesSchema, { lockState: 1 })
|
|
103
|
-
const decoded = fromBinary(DoorLockPropertiesSchema, toBinary(DoorLockPropertiesSchema, p))
|
|
104
|
-
expect(decoded.lockState).toBe(1)
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
describe('ContactSensorProperties — Matter Boolean State 0x0045', () => {
|
|
109
|
-
it('round-trips state_value false (contact open = alarm)', () => {
|
|
110
|
-
const p = create(ContactSensorPropertiesSchema, { stateValue: false })
|
|
111
|
-
const decoded = fromBinary(ContactSensorPropertiesSchema, toBinary(ContactSensorPropertiesSchema, p))
|
|
112
|
-
expect(decoded.stateValue).toBe(false)
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe('OnOffLightProperties — Matter On/Off 0x0006', () => {
|
|
117
|
-
it('round-trips on_off true', () => {
|
|
118
|
-
const p = create(OnOffLightPropertiesSchema, { onOff: true })
|
|
119
|
-
const decoded = fromBinary(OnOffLightPropertiesSchema, toBinary(OnOffLightPropertiesSchema, p))
|
|
120
|
-
expect(decoded.onOff).toBe(true)
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
describe('DimmableLightProperties — Matter Level Control 0x0008', () => {
|
|
125
|
-
it('round-trips current_level (0–254)', () => {
|
|
126
|
-
const p = create(DimmableLightPropertiesSchema, { onOff: true, currentLevel: 127 })
|
|
127
|
-
const decoded = fromBinary(DimmableLightPropertiesSchema, toBinary(DimmableLightPropertiesSchema, p))
|
|
128
|
-
expect(decoded.currentLevel).toBe(127)
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
describe('Device oneof properties', () => {
|
|
133
|
-
it('stores thermostat properties in the oneof', () => {
|
|
134
|
-
const d = create(DeviceSchema, {
|
|
135
|
-
descriptor: { deviceId: { value: 'therm-1' }, matterDeviceTypeId: 0x0301 },
|
|
136
|
-
properties: { case: 'thermostat', value: { occupiedHeatingSetpoint: 2000 } },
|
|
137
|
-
})
|
|
138
|
-
const decoded = fromBinary(DeviceSchema, toBinary(DeviceSchema, d))
|
|
139
|
-
expect(decoded.properties?.case).toBe('thermostat')
|
|
140
|
-
if (decoded.properties?.case === 'thermostat') {
|
|
141
|
-
expect(decoded.properties.value.occupiedHeatingSetpoint).toBe(2000)
|
|
142
|
-
}
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('stores temperature sensor properties in the oneof', () => {
|
|
146
|
-
const d = create(DeviceSchema, {
|
|
147
|
-
descriptor: { deviceId: { value: 'temp-1' }, matterDeviceTypeId: 0x0302 },
|
|
148
|
-
properties: { case: 'temperatureSensor', value: { measuredValue: 1950 } },
|
|
149
|
-
})
|
|
150
|
-
const decoded = fromBinary(DeviceSchema, toBinary(DeviceSchema, d))
|
|
151
|
-
expect(decoded.properties?.case).toBe('temperatureSensor')
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
describe('PropertyUpdate', () => {
|
|
156
|
-
it('round-trips int_value for thermostat setpoint', () => {
|
|
157
|
-
const u = create(PropertyUpdateSchema, {
|
|
158
|
-
deviceId: { value: 'therm-1' },
|
|
159
|
-
attributeName: 'OccupiedHeatingSetpoint',
|
|
160
|
-
value: { case: 'intValue', value: 2150 },
|
|
161
|
-
clusterIdHex: '0201',
|
|
162
|
-
})
|
|
163
|
-
const decoded = fromBinary(PropertyUpdateSchema, toBinary(PropertyUpdateSchema, u))
|
|
164
|
-
expect(decoded.attributeName).toBe('OccupiedHeatingSetpoint')
|
|
165
|
-
expect(decoded.value?.case).toBe('intValue')
|
|
166
|
-
if (decoded.value?.case === 'intValue') expect(decoded.value.value).toBe(2150)
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('round-trips bool_value for on/off light', () => {
|
|
170
|
-
const u = create(PropertyUpdateSchema, {
|
|
171
|
-
deviceId: { value: 'light-1' },
|
|
172
|
-
attributeName: 'OnOff',
|
|
173
|
-
value: { case: 'boolValue', value: true },
|
|
174
|
-
clusterIdHex: '0006',
|
|
175
|
-
})
|
|
176
|
-
const decoded = fromBinary(PropertyUpdateSchema, toBinary(PropertyUpdateSchema, u))
|
|
177
|
-
expect(decoded.value?.case).toBe('boolValue')
|
|
178
|
-
if (decoded.value?.case === 'boolValue') expect(decoded.value.value).toBe(true)
|
|
179
|
-
})
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
describe('PropertyUpdateBatch', () => {
|
|
183
|
-
it('round-trips multiple updates', () => {
|
|
184
|
-
const batch = create(PropertyUpdateBatchSchema, {
|
|
185
|
-
updates: [
|
|
186
|
-
{ deviceId: { value: 'd1' }, attributeName: 'LocalTemperature', value: { case: 'intValue', value: 1900 } },
|
|
187
|
-
{ deviceId: { value: 'd1' }, attributeName: 'OccupiedHeatingSetpoint', value: { case: 'intValue', value: 2100 } },
|
|
188
|
-
],
|
|
189
|
-
})
|
|
190
|
-
const decoded = fromBinary(PropertyUpdateBatchSchema, toBinary(PropertyUpdateBatchSchema, batch))
|
|
191
|
-
expect(decoded.updates.length).toBe(2)
|
|
192
|
-
expect(decoded.updates[0].attributeName).toBe('LocalTemperature')
|
|
193
|
-
})
|
|
194
|
-
})
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import {
|
|
5
|
-
DeviceIdSchema,
|
|
6
|
-
GatewayIdSchema,
|
|
7
|
-
UserIdSchema,
|
|
8
|
-
SpaceIdSchema,
|
|
9
|
-
ConnectorIdSchema,
|
|
10
|
-
TenantIdSchema,
|
|
11
|
-
} from '../kusinta/iot/identity/v1/identity_pb.js'
|
|
12
|
-
|
|
13
|
-
describe('Identity wrapper types', () => {
|
|
14
|
-
it('DeviceId round-trips value', () => {
|
|
15
|
-
const id = create(DeviceIdSchema, { value: 'device-abc-123' })
|
|
16
|
-
expect(fromBinary(DeviceIdSchema, toBinary(DeviceIdSchema, id)).value).toBe('device-abc-123')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('GatewayId round-trips value', () => {
|
|
20
|
-
const id = create(GatewayIdSchema, { value: 'gw-hmac-sha256-result' })
|
|
21
|
-
expect(fromBinary(GatewayIdSchema, toBinary(GatewayIdSchema, id)).value).toBe('gw-hmac-sha256-result')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('UserId round-trips value', () => {
|
|
25
|
-
const id = create(UserIdSchema, { value: 'user-keycloak-sub' })
|
|
26
|
-
expect(fromBinary(UserIdSchema, toBinary(UserIdSchema, id)).value).toBe('user-keycloak-sub')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('SpaceId round-trips value', () => {
|
|
30
|
-
const id = create(SpaceIdSchema, { value: 'space-uuid-v4' })
|
|
31
|
-
expect(fromBinary(SpaceIdSchema, toBinary(SpaceIdSchema, id)).value).toBe('space-uuid-v4')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('ConnectorId round-trips value', () => {
|
|
35
|
-
const id = create(ConnectorIdSchema, { value: 'homematic-ccu3' })
|
|
36
|
-
expect(fromBinary(ConnectorIdSchema, toBinary(ConnectorIdSchema, id)).value).toBe('homematic-ccu3')
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('TenantId round-trips value', () => {
|
|
40
|
-
const id = create(TenantIdSchema, { value: 'tenant-property-co' })
|
|
41
|
-
expect(fromBinary(TenantIdSchema, toBinary(TenantIdSchema, id)).value).toBe('tenant-property-co')
|
|
42
|
-
})
|
|
43
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import { SpaceType } from '../kusinta/iot/common/v1/types_pb.js'
|
|
5
|
-
import { SpaceSchema } from '../kusinta/iot/space/v1/space_pb.js'
|
|
6
|
-
|
|
7
|
-
describe('Space', () => {
|
|
8
|
-
it('round-trips a building with sub-spaces', () => {
|
|
9
|
-
const building = create(SpaceSchema, {
|
|
10
|
-
spaceId: { value: 'building-1' },
|
|
11
|
-
spaceType: SpaceType.BUILDING,
|
|
12
|
-
name: 'Storgatan 5',
|
|
13
|
-
tenantId: { value: 'tenant-1' },
|
|
14
|
-
gatewayId: { value: 'gw-1' },
|
|
15
|
-
subSpaceIds: [{ value: 'floor-1' }, { value: 'floor-2' }],
|
|
16
|
-
})
|
|
17
|
-
const decoded = fromBinary(SpaceSchema, toBinary(SpaceSchema, building))
|
|
18
|
-
expect(decoded.spaceId?.value).toBe('building-1')
|
|
19
|
-
expect(decoded.spaceType).toBe(SpaceType.BUILDING)
|
|
20
|
-
expect(decoded.name).toBe('Storgatan 5')
|
|
21
|
-
expect(decoded.subSpaceIds).toHaveLength(2)
|
|
22
|
-
expect(decoded.subSpaceIds[0].value).toBe('floor-1')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('round-trips an apartment with resident', () => {
|
|
26
|
-
const apt = create(SpaceSchema, {
|
|
27
|
-
spaceId: { value: 'apt-101' },
|
|
28
|
-
spaceType: SpaceType.APARTMENT,
|
|
29
|
-
name: 'Lgh 101',
|
|
30
|
-
floor: 1,
|
|
31
|
-
parentSpaceId: { value: 'floor-1' },
|
|
32
|
-
residentUserId: { value: 'user-resident-1' },
|
|
33
|
-
deviceIds: [{ value: 'therm-1' }, { value: 'temp-sensor-1' }],
|
|
34
|
-
})
|
|
35
|
-
const decoded = fromBinary(SpaceSchema, toBinary(SpaceSchema, apt))
|
|
36
|
-
expect(decoded.spaceType).toBe(SpaceType.APARTMENT)
|
|
37
|
-
expect(decoded.floor).toBe(1)
|
|
38
|
-
expect(decoded.parentSpaceId?.value).toBe('floor-1')
|
|
39
|
-
expect(decoded.residentUserId?.value).toBe('user-resident-1')
|
|
40
|
-
expect(decoded.deviceIds).toHaveLength(2)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('round-trips a room', () => {
|
|
44
|
-
const room = create(SpaceSchema, {
|
|
45
|
-
spaceId: { value: 'room-living' },
|
|
46
|
-
spaceType: SpaceType.ROOM,
|
|
47
|
-
name: 'Living Room',
|
|
48
|
-
parentSpaceId: { value: 'apt-101' },
|
|
49
|
-
})
|
|
50
|
-
const decoded = fromBinary(SpaceSchema, toBinary(SpaceSchema, room))
|
|
51
|
-
expect(decoded.spaceType).toBe(SpaceType.ROOM)
|
|
52
|
-
expect(decoded.name).toBe('Living Room')
|
|
53
|
-
})
|
|
54
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import {
|
|
5
|
-
HomematicVendorExtensionSchema,
|
|
6
|
-
HmThermostatPropsSchema,
|
|
7
|
-
} from '../kusinta/iot/vendor/homematic/v1/homematic_pb.js'
|
|
8
|
-
import { DeviceSchema } from '../kusinta/iot/device/v1/device_pb.js'
|
|
9
|
-
|
|
10
|
-
describe('HmThermostatProps', () => {
|
|
11
|
-
it('round-trips boost_mode and control_mode', () => {
|
|
12
|
-
const p = create(HmThermostatPropsSchema, {
|
|
13
|
-
boostMode: true, boostTimePeriod: 15.5, controlMode: 2, frostProtection: false,
|
|
14
|
-
})
|
|
15
|
-
const decoded = fromBinary(HmThermostatPropsSchema, toBinary(HmThermostatPropsSchema, p))
|
|
16
|
-
expect(decoded.boostMode).toBe(true)
|
|
17
|
-
expect(decoded.boostTimePeriod).toBeCloseTo(15.5, 3)
|
|
18
|
-
expect(decoded.controlMode).toBe(2)
|
|
19
|
-
expect(decoded.frostProtection).toBe(false)
|
|
20
|
-
})
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
describe('HomematicVendorExtension', () => {
|
|
24
|
-
it('round-trips homematic_address and thermostat props', () => {
|
|
25
|
-
const ext = create(HomematicVendorExtensionSchema, {
|
|
26
|
-
homematicAddress: 'MEQ1234567',
|
|
27
|
-
homematicType: 'HmIP-eTRV-2',
|
|
28
|
-
homematicProps: { case: 'hmThermostat', value: { boostMode: false, controlMode: 1, currentProfilePeriod: 3.0 } },
|
|
29
|
-
})
|
|
30
|
-
const decoded = fromBinary(HomematicVendorExtensionSchema, toBinary(HomematicVendorExtensionSchema, ext))
|
|
31
|
-
expect(decoded.homematicAddress).toBe('MEQ1234567')
|
|
32
|
-
expect(decoded.homematicType).toBe('HmIP-eTRV-2')
|
|
33
|
-
expect(decoded.homematicProps?.case).toBe('hmThermostat')
|
|
34
|
-
if (decoded.homematicProps?.case === 'hmThermostat') {
|
|
35
|
-
expect(decoded.homematicProps.value.controlMode).toBe(1)
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
describe('Device with HomematicVendorExtension (field 50)', () => {
|
|
42
|
-
it('stores homematic extension in the oneof', () => {
|
|
43
|
-
const d = create(DeviceSchema, {
|
|
44
|
-
descriptor: { deviceId: { value: 'hm-therm-1' }, matterDeviceTypeId: 0x0301 },
|
|
45
|
-
properties: {
|
|
46
|
-
case: 'homematic',
|
|
47
|
-
value: {
|
|
48
|
-
homematicAddress: 'MEQ1234567',
|
|
49
|
-
homematicType: 'HmIP-eTRV-2',
|
|
50
|
-
homematicProps: { case: 'hmThermostat', value: { controlMode: 2 } },
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
const decoded = fromBinary(DeviceSchema, toBinary(DeviceSchema, d))
|
|
55
|
-
expect(decoded.properties?.case).toBe('homematic')
|
|
56
|
-
if (decoded.properties?.case === 'homematic') {
|
|
57
|
-
expect(decoded.properties.value.homematicAddress).toBe('MEQ1234567')
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
})
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import { create, toBinary, fromBinary } from '@bufbuild/protobuf'
|
|
4
|
-
import {
|
|
5
|
-
GatewayMessageSchema,
|
|
6
|
-
AppMessageSchema,
|
|
7
|
-
AppHandshakeSchema,
|
|
8
|
-
} from '../kusinta/iot/webrtc/v1/envelope_pb.js'
|
|
9
|
-
import { DeviceCommandSchema, CommandResultSchema } from '../kusinta/iot/webrtc/v1/command_pb.js'
|
|
10
|
-
import { DeviceStateSnapshotSchema, DevicePropertyEventSchema } from '../kusinta/iot/webrtc/v1/device_state_pb.js'
|
|
11
|
-
import { LivePermissionUpdateSchema } from '../kusinta/iot/webrtc/v1/permission_push_pb.js'
|
|
12
|
-
|
|
13
|
-
describe('AppHandshake', () => {
|
|
14
|
-
it('round-trips jwt and subscribe_device_ids', () => {
|
|
15
|
-
const h = create(AppHandshakeSchema, {
|
|
16
|
-
jwt: 'eyJhbGciOiJSUzI1NiJ9.test.sig',
|
|
17
|
-
subscribeDeviceIds: [{ value: 'dev-1' }, { value: 'dev-2' }],
|
|
18
|
-
})
|
|
19
|
-
const decoded = fromBinary(AppHandshakeSchema, toBinary(AppHandshakeSchema, h))
|
|
20
|
-
expect(decoded.jwt).toBe('eyJhbGciOiJSUzI1NiJ9.test.sig')
|
|
21
|
-
expect(decoded.subscribeDeviceIds).toHaveLength(2)
|
|
22
|
-
expect(decoded.subscribeDeviceIds[0].value).toBe('dev-1')
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
describe('DeviceCommand', () => {
|
|
27
|
-
it('round-trips thermostat setpoint command', () => {
|
|
28
|
-
const cmd = create(DeviceCommandSchema, {
|
|
29
|
-
commandId: 'cmd-uuid-1',
|
|
30
|
-
deviceId: { value: 'therm-1' },
|
|
31
|
-
clusterIdHex: '0201',
|
|
32
|
-
commandName: 'SetpointRaiseLower',
|
|
33
|
-
parameters: { case: 'thermostatSetpoint', value: { mode: 4, amount: 50 } },
|
|
34
|
-
})
|
|
35
|
-
const decoded = fromBinary(DeviceCommandSchema, toBinary(DeviceCommandSchema, cmd))
|
|
36
|
-
expect(decoded.commandId).toBe('cmd-uuid-1')
|
|
37
|
-
expect(decoded.deviceId?.value).toBe('therm-1')
|
|
38
|
-
expect(decoded.commandName).toBe('SetpointRaiseLower')
|
|
39
|
-
expect(decoded.parameters?.case).toBe('thermostatSetpoint')
|
|
40
|
-
if (decoded.parameters?.case === 'thermostatSetpoint') {
|
|
41
|
-
expect(decoded.parameters.value.mode).toBe(4)
|
|
42
|
-
expect(decoded.parameters.value.amount).toBe(50)
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('round-trips on/off command', () => {
|
|
47
|
-
const cmd = create(DeviceCommandSchema, {
|
|
48
|
-
commandId: 'cmd-uuid-2',
|
|
49
|
-
deviceId: { value: 'light-1' },
|
|
50
|
-
clusterIdHex: '0006',
|
|
51
|
-
commandName: 'Toggle',
|
|
52
|
-
parameters: { case: 'onOff', value: { toggle: true } },
|
|
53
|
-
})
|
|
54
|
-
const decoded = fromBinary(DeviceCommandSchema, toBinary(DeviceCommandSchema, cmd))
|
|
55
|
-
expect(decoded.parameters?.case).toBe('onOff')
|
|
56
|
-
if (decoded.parameters?.case === 'onOff') {
|
|
57
|
-
expect(decoded.parameters.value.toggle).toBe(true)
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('round-trips level control command', () => {
|
|
62
|
-
const cmd = create(DeviceCommandSchema, {
|
|
63
|
-
commandId: 'cmd-uuid-3',
|
|
64
|
-
deviceId: { value: 'light-2' },
|
|
65
|
-
clusterIdHex: '0008',
|
|
66
|
-
commandName: 'MoveToLevel',
|
|
67
|
-
parameters: { case: 'levelControl', value: { level: 127, transitionTime: 10 } },
|
|
68
|
-
})
|
|
69
|
-
const decoded = fromBinary(DeviceCommandSchema, toBinary(DeviceCommandSchema, cmd))
|
|
70
|
-
expect(decoded.parameters?.case).toBe('levelControl')
|
|
71
|
-
if (decoded.parameters?.case === 'levelControl') {
|
|
72
|
-
expect(decoded.parameters.value.level).toBe(127)
|
|
73
|
-
expect(decoded.parameters.value.transitionTime).toBe(10)
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('round-trips window covering lift command', () => {
|
|
78
|
-
const cmd = create(DeviceCommandSchema, {
|
|
79
|
-
commandId: 'cmd-uuid-4',
|
|
80
|
-
deviceId: { value: 'blind-1' },
|
|
81
|
-
clusterIdHex: '0102',
|
|
82
|
-
commandName: 'GoToLiftPercentage',
|
|
83
|
-
parameters: { case: 'windowCoveringLift', value: { liftPercent100ths: 5000 } },
|
|
84
|
-
})
|
|
85
|
-
const decoded = fromBinary(DeviceCommandSchema, toBinary(DeviceCommandSchema, cmd))
|
|
86
|
-
expect(decoded.parameters?.case).toBe('windowCoveringLift')
|
|
87
|
-
if (decoded.parameters?.case === 'windowCoveringLift') {
|
|
88
|
-
expect(decoded.parameters.value.liftPercent100ths).toBe(5000)
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('round-trips door lock command', () => {
|
|
93
|
-
const cmd = create(DeviceCommandSchema, {
|
|
94
|
-
commandId: 'cmd-uuid-5',
|
|
95
|
-
deviceId: { value: 'lock-1' },
|
|
96
|
-
clusterIdHex: '0101',
|
|
97
|
-
commandName: 'LockDoor',
|
|
98
|
-
parameters: { case: 'doorLock', value: { lockState: 1 } },
|
|
99
|
-
})
|
|
100
|
-
const decoded = fromBinary(DeviceCommandSchema, toBinary(DeviceCommandSchema, cmd))
|
|
101
|
-
expect(decoded.parameters?.case).toBe('doorLock')
|
|
102
|
-
if (decoded.parameters?.case === 'doorLock') {
|
|
103
|
-
expect(decoded.parameters.value.lockState).toBe(1)
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
describe('CommandResult', () => {
|
|
109
|
-
it('round-trips successful result', () => {
|
|
110
|
-
const result = create(CommandResultSchema, { commandId: 'cmd-uuid-1', success: true })
|
|
111
|
-
const decoded = fromBinary(CommandResultSchema, toBinary(CommandResultSchema, result))
|
|
112
|
-
expect(decoded.commandId).toBe('cmd-uuid-1')
|
|
113
|
-
expect(decoded.success).toBe(true)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('round-trips failed result with error', () => {
|
|
117
|
-
const result = create(CommandResultSchema, {
|
|
118
|
-
commandId: 'cmd-uuid-2',
|
|
119
|
-
success: false,
|
|
120
|
-
error: { code: 'PERMISSION_DENIED', message: 'Setpoint exceeds property-owner limit' },
|
|
121
|
-
})
|
|
122
|
-
const decoded = fromBinary(CommandResultSchema, toBinary(CommandResultSchema, result))
|
|
123
|
-
expect(decoded.success).toBe(false)
|
|
124
|
-
expect(decoded.error?.code).toBe('PERMISSION_DENIED')
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
describe('DeviceStateSnapshot', () => {
|
|
129
|
-
it('round-trips with devices and permissions', () => {
|
|
130
|
-
const snap = create(DeviceStateSnapshotSchema, {
|
|
131
|
-
devices: [{
|
|
132
|
-
descriptor: { deviceId: { value: 'therm-1' }, matterDeviceTypeId: 0x0301 },
|
|
133
|
-
properties: { case: 'thermostat', value: { occupiedHeatingSetpoint: 2100 } },
|
|
134
|
-
}],
|
|
135
|
-
permissions: { userId: { value: 'user-1' }, gatewayId: { value: 'gw-1' }, deviceAcls: [] },
|
|
136
|
-
})
|
|
137
|
-
const decoded = fromBinary(DeviceStateSnapshotSchema, toBinary(DeviceStateSnapshotSchema, snap))
|
|
138
|
-
expect(decoded.devices).toHaveLength(1)
|
|
139
|
-
expect(decoded.devices[0].descriptor?.deviceId?.value).toBe('therm-1')
|
|
140
|
-
expect(decoded.permissions?.userId?.value).toBe('user-1')
|
|
141
|
-
})
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
describe('DevicePropertyEvent', () => {
|
|
145
|
-
it('round-trips a property update event', () => {
|
|
146
|
-
const event = create(DevicePropertyEventSchema, {
|
|
147
|
-
update: {
|
|
148
|
-
deviceId: { value: 'therm-1' },
|
|
149
|
-
attributeName: 'LocalTemperature',
|
|
150
|
-
value: { case: 'intValue', value: 2050 },
|
|
151
|
-
clusterIdHex: '0402',
|
|
152
|
-
},
|
|
153
|
-
})
|
|
154
|
-
const decoded = fromBinary(DevicePropertyEventSchema, toBinary(DevicePropertyEventSchema, event))
|
|
155
|
-
expect(decoded.update?.attributeName).toBe('LocalTemperature')
|
|
156
|
-
expect(decoded.update?.value?.case).toBe('intValue')
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
describe('LivePermissionUpdate', () => {
|
|
161
|
-
it('round-trips added and removed devices', () => {
|
|
162
|
-
const update = create(LivePermissionUpdateSchema, {
|
|
163
|
-
newPermissions: { userId: { value: 'user-1' }, gatewayId: { value: 'gw-1' }, deviceAcls: [] },
|
|
164
|
-
addedDevices: [{ value: 'dev-new-1' }],
|
|
165
|
-
removedDevices: [{ value: 'dev-old-1' }],
|
|
166
|
-
changeReason: 'DEVICE_ASSIGNED',
|
|
167
|
-
})
|
|
168
|
-
const decoded = fromBinary(LivePermissionUpdateSchema, toBinary(LivePermissionUpdateSchema, update))
|
|
169
|
-
expect(decoded.addedDevices).toHaveLength(1)
|
|
170
|
-
expect(decoded.addedDevices[0].value).toBe('dev-new-1')
|
|
171
|
-
expect(decoded.removedDevices).toHaveLength(1)
|
|
172
|
-
expect(decoded.changeReason).toBe('DEVICE_ASSIGNED')
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
describe('GatewayMessage oneof payload', () => {
|
|
177
|
-
it('round-trips state_snapshot payload', () => {
|
|
178
|
-
const msg = create(GatewayMessageSchema, {
|
|
179
|
-
messageId: 'gw-msg-001',
|
|
180
|
-
payload: {
|
|
181
|
-
case: 'stateSnapshot',
|
|
182
|
-
value: {
|
|
183
|
-
devices: [],
|
|
184
|
-
permissions: { userId: { value: 'user-1' }, gatewayId: { value: 'gw-1' }, deviceAcls: [] },
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
})
|
|
188
|
-
const decoded = fromBinary(GatewayMessageSchema, toBinary(GatewayMessageSchema, msg))
|
|
189
|
-
expect(decoded.messageId).toBe('gw-msg-001')
|
|
190
|
-
expect(decoded.payload?.case).toBe('stateSnapshot')
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('round-trips property_event payload', () => {
|
|
194
|
-
const msg = create(GatewayMessageSchema, {
|
|
195
|
-
messageId: 'gw-msg-002',
|
|
196
|
-
payload: {
|
|
197
|
-
case: 'propertyEvent',
|
|
198
|
-
value: {
|
|
199
|
-
update: {
|
|
200
|
-
deviceId: { value: 'therm-1' },
|
|
201
|
-
attributeName: 'OccupiedHeatingSetpoint',
|
|
202
|
-
value: { case: 'intValue', value: 2100 },
|
|
203
|
-
clusterIdHex: '0201',
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
})
|
|
208
|
-
const decoded = fromBinary(GatewayMessageSchema, toBinary(GatewayMessageSchema, msg))
|
|
209
|
-
expect(decoded.payload?.case).toBe('propertyEvent')
|
|
210
|
-
if (decoded.payload?.case === 'propertyEvent') {
|
|
211
|
-
expect(decoded.payload.value.update?.attributeName).toBe('OccupiedHeatingSetpoint')
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('round-trips pong payload', () => {
|
|
216
|
-
const msg = create(GatewayMessageSchema, {
|
|
217
|
-
messageId: 'gw-msg-003',
|
|
218
|
-
payload: { case: 'pong', value: {} },
|
|
219
|
-
})
|
|
220
|
-
const decoded = fromBinary(GatewayMessageSchema, toBinary(GatewayMessageSchema, msg))
|
|
221
|
-
expect(decoded.payload?.case).toBe('pong')
|
|
222
|
-
})
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
describe('AppMessage oneof payload', () => {
|
|
226
|
-
it('round-trips handshake payload', () => {
|
|
227
|
-
const msg = create(AppMessageSchema, {
|
|
228
|
-
messageId: 'app-msg-001',
|
|
229
|
-
payload: {
|
|
230
|
-
case: 'handshake',
|
|
231
|
-
value: { jwt: 'token.payload.sig', subscribeDeviceIds: [{ value: 'dev-1' }] },
|
|
232
|
-
},
|
|
233
|
-
})
|
|
234
|
-
const decoded = fromBinary(AppMessageSchema, toBinary(AppMessageSchema, msg))
|
|
235
|
-
expect(decoded.messageId).toBe('app-msg-001')
|
|
236
|
-
expect(decoded.payload?.case).toBe('handshake')
|
|
237
|
-
if (decoded.payload?.case === 'handshake') {
|
|
238
|
-
expect(decoded.payload.value.jwt).toBe('token.payload.sig')
|
|
239
|
-
}
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
it('round-trips command payload', () => {
|
|
243
|
-
const msg = create(AppMessageSchema, {
|
|
244
|
-
messageId: 'app-msg-002',
|
|
245
|
-
payload: {
|
|
246
|
-
case: 'command',
|
|
247
|
-
value: {
|
|
248
|
-
commandId: 'cmd-1',
|
|
249
|
-
deviceId: { value: 'light-1' },
|
|
250
|
-
clusterIdHex: '0006',
|
|
251
|
-
commandName: 'Toggle',
|
|
252
|
-
parameters: { case: 'onOff', value: { toggle: true } },
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
})
|
|
256
|
-
const decoded = fromBinary(AppMessageSchema, toBinary(AppMessageSchema, msg))
|
|
257
|
-
expect(decoded.payload?.case).toBe('command')
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('round-trips ping payload', () => {
|
|
261
|
-
const msg = create(AppMessageSchema, {
|
|
262
|
-
messageId: 'app-msg-003',
|
|
263
|
-
payload: { case: 'ping', value: {} },
|
|
264
|
-
})
|
|
265
|
-
const decoded = fromBinary(AppMessageSchema, toBinary(AppMessageSchema, msg))
|
|
266
|
-
expect(decoded.payload?.case).toBe('ping')
|
|
267
|
-
})
|
|
268
|
-
})
|