aws-iot-device-sdk-v2
Version:
NodeJS API for the AWS IoT service
414 lines (332 loc) • 12.9 kB
text/typescript
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
import {iot, mqtt5, mqtt as mqtt311, mqtt_request_response} from "aws-crt";
import {v4 as uuid} from "uuid";
import {StreamingOperation} from "../mqtt_request_response";
import {once} from "events";
import {IotShadowClientv2} from "./iotshadowclientv2";
import * as model from "./model";
jest.setTimeout(10000);
function hasTestEnvironment() : boolean {
if (process.env.AWS_TEST_MQTT5_IOT_CORE_HOST === undefined) {
return false;
}
if (process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH === undefined) {
return false;
}
if (process.env.AWS_TEST_MQTT5_IOT_KEY_PATH === undefined) {
return false;
}
return true;
}
const conditional_test = (condition : boolean) => condition ? it : it.skip;
function build_protocol_client_mqtt5() : mqtt5.Mqtt5Client {
let builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath(
// @ts-ignore
process.env.AWS_TEST_MQTT5_IOT_CORE_HOST,
process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH,
process.env.AWS_TEST_MQTT5_IOT_KEY_PATH
);
builder.withConnectProperties({
clientId : `test-${uuid()}`,
keepAliveIntervalSeconds: 1200,
});
return new mqtt5.Mqtt5Client(builder.build());
}
function build_protocol_client_mqtt311() : mqtt311.MqttClientConnection {
// @ts-ignore
let builder = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder_from_path(process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH, process.env.AWS_TEST_MQTT5_IOT_KEY_PATH);
// @ts-ignore
builder.with_endpoint(process.env.AWS_TEST_MQTT5_IOT_CORE_HOST);
builder.with_client_id(`test-${uuid()}`);
let client = new mqtt311.MqttClient();
return client.new_connection(builder.build());
}
enum ProtocolVersion {
Mqtt311,
Mqtt5
}
interface TestingOptions {
version: ProtocolVersion,
timeoutSeconds?: number,
}
class ShadowTestingContext {
mqtt311Client?: mqtt311.MqttClientConnection;
mqtt5Client?: mqtt5.Mqtt5Client;
client: IotShadowClientv2;
private protocolStarted : boolean = false;
async startProtocolClient() {
if (!this.protocolStarted) {
this.protocolStarted = true;
if (this.mqtt5Client) {
let connected = once(this.mqtt5Client, mqtt5.Mqtt5Client.CONNECTION_SUCCESS);
this.mqtt5Client.start();
await connected;
}
if (this.mqtt311Client) {
await this.mqtt311Client.connect();
}
}
}
async stopProtocolClient() {
if (this.protocolStarted) {
this.protocolStarted = false;
if (this.mqtt5Client) {
let stopped = once(this.mqtt5Client, mqtt5.Mqtt5Client.STOPPED);
this.mqtt5Client.stop();
await stopped;
this.mqtt5Client.close();
}
if (this.mqtt311Client) {
await this.mqtt311Client.disconnect();
}
}
}
constructor(options: TestingOptions) {
if (options.version == ProtocolVersion.Mqtt5) {
this.mqtt5Client = build_protocol_client_mqtt5();
let rrOptions : mqtt_request_response.RequestResponseClientOptions = {
maxRequestResponseSubscriptions : 6,
maxStreamingSubscriptions : 2,
operationTimeoutInSeconds : options.timeoutSeconds ?? 60,
}
this.client = IotShadowClientv2.newFromMqtt5(this.mqtt5Client, rrOptions);
} else {
this.mqtt311Client = build_protocol_client_mqtt311();
let rrOptions : mqtt_request_response.RequestResponseClientOptions = {
maxRequestResponseSubscriptions : 6,
maxStreamingSubscriptions : 2,
operationTimeoutInSeconds : options.timeoutSeconds ?? 60,
}
this.client = IotShadowClientv2.newFromMqtt311(this.mqtt311Client, rrOptions);
}
}
async open() {
await this.startProtocolClient();
}
async close() {
this.client.close();
await this.stopProtocolClient();
}
}
async function doCreateDestroyTest(version: ProtocolVersion) {
let context = new ShadowTestingContext({
version: version
});
await context.open();
await context.close();
}
test('shadowv2 - create destroy mqtt5', async () => {
if (hasTestEnvironment()) {
await doCreateDestroyTest(ProtocolVersion.Mqtt5);
}
});
conditional_test(hasTestEnvironment())('shadowv2 - create destroy mqtt311', async () => {
await doCreateDestroyTest(ProtocolVersion.Mqtt311);
});
async function getNonexistentShadow(client: IotShadowClientv2, thingName: string, shadowName: string) {
let request : model.GetNamedShadowRequest = {
thingName: thingName,
shadowName: shadowName,
};
try {
await client.getNamedShadow(request);
expect(false);
} catch (err: any) {
expect(err.message).toContain("Request failed");
expect(err.modeledError).toBeDefined();
expect(err.modeledError.code).toEqual(404);
expect(err.modeledError.message).toContain("No shadow exists with name");
}
}
async function doGetNonexistentShadowTest(version: ProtocolVersion) {
let context = new ShadowTestingContext({
version: version
});
await context.open();
await getNonexistentShadow(context.client, uuid(), uuid());
await context.close();
}
conditional_test(hasTestEnvironment())('shadowv2 - get non-existent shadow mqtt5', async () => {
await doGetNonexistentShadowTest(ProtocolVersion.Mqtt5);
});
conditional_test(hasTestEnvironment())('shadowv2 - get non-existent shadow mqtt311', async () => {
await doGetNonexistentShadowTest(ProtocolVersion.Mqtt311);
});
async function createShadow(client: IotShadowClientv2, thingName: string, shadowName: string, document: any) {
let request : model.UpdateNamedShadowRequest = {
thingName: thingName,
shadowName: shadowName,
state: {
desired: document,
reported: document,
}
};
try {
let response = await client.updateNamedShadow(request);
expect(response.state).toBeDefined();
// @ts-ignore
expect(response.state.desired).toBeDefined();
// @ts-ignore
expect(response.state.desired).toEqual(document);
// @ts-ignore
expect(response.state.reported).toBeDefined();
// @ts-ignore
expect(response.state.reported).toEqual(document);
} catch (err) {
throw err;
}
}
async function getShadow(client: IotShadowClientv2, thingName: string, shadowName: string, expectedDocument: any) {
let request : model.GetNamedShadowRequest = {
thingName: thingName,
shadowName: shadowName,
};
let response = await client.getNamedShadow(request);
expect(response.state).toBeDefined();
// @ts-ignore
expect(response.state.desired).toBeDefined();
// @ts-ignore
expect(response.state.desired).toEqual(expectedDocument);
// @ts-ignore
expect(response.state.reported).toBeDefined();
// @ts-ignore
expect(response.state.reported).toEqual(expectedDocument);
}
async function deleteShadow(client: IotShadowClientv2, thingName: string, shadowName: string, expectedVersion: number) {
let request : model.DeleteNamedShadowRequest = {
thingName: thingName,
shadowName: shadowName,
};
let response = await client.deleteNamedShadow(request);
expect(response.version).toEqual(expectedVersion);
}
async function doCreateDeleteShadowTest(version: ProtocolVersion) {
let context = new ShadowTestingContext({
version: version
});
await context.open();
let thingName = uuid();
let shadowName = uuid();
let document = {
color: "green",
on: true,
};
// shouldn't exist yet
await getNonexistentShadow(context.client, thingName, shadowName);
try {
await createShadow(context.client, thingName, shadowName, document);
await getShadow(context.client, thingName, shadowName, document);
} finally {
await deleteShadow(context.client, thingName, shadowName, 1);
}
await getNonexistentShadow(context.client, thingName, shadowName);
await context.close();
}
conditional_test(hasTestEnvironment())('shadowv2 - create-destroy shadow mqtt5', async () => {
await doCreateDeleteShadowTest(ProtocolVersion.Mqtt5);
});
conditional_test(hasTestEnvironment())('shadowv2 - create-destroy shadow mqtt311', async () => {
await doCreateDeleteShadowTest(ProtocolVersion.Mqtt311);
});
async function updateShadowDesired(client: IotShadowClientv2, thingName: string, shadowName: string, document: any) {
let request : model.UpdateNamedShadowRequest = {
thingName: thingName,
shadowName: shadowName,
state: {
desired: document,
},
};
try {
let response = await client.updateNamedShadow(request);
expect(response.state).toBeDefined();
// @ts-ignore
expect(response.state.desired).toBeDefined();
// @ts-ignore
expect(response.state.desired).toEqual(document);
} catch (err) {
throw err;
}
}
async function updateShadowReported(client: IotShadowClientv2, thingName: string, shadowName: string, document: any) {
let request : model.UpdateNamedShadowRequest = {
thingName: thingName,
shadowName: shadowName,
state: {
reported: document,
},
};
try {
let response = await client.updateNamedShadow(request);
expect(response.state).toBeDefined();
// @ts-ignore
expect(response.state.reported).toBeDefined();
// @ts-ignore
expect(response.state.reported).toEqual(document);
} catch (err) {
throw err;
}
}
async function doUpdateShadowTest(version: ProtocolVersion) {
let context = new ShadowTestingContext({
version: version
});
await context.open();
let thingName = uuid();
let shadowName = uuid();
let document = {
color: "green",
on: true,
};
let deltaEventStream = context.client.createNamedShadowDeltaUpdatedStream({
thingName: thingName,
shadowName: shadowName,
});
let deltaStatusUpdate = once(deltaEventStream, StreamingOperation.SUBSCRIPTION_STATUS);
deltaEventStream.open();
let deltaStatus = (await deltaStatusUpdate)[0];
expect(deltaStatus.type).toEqual(mqtt_request_response.SubscriptionStatusEventType.SubscriptionEstablished);
let stateStream = context.client.createNamedShadowDeltaUpdatedStream({
thingName: thingName,
shadowName: shadowName,
});
let stateStatusUpdate = once(stateStream, StreamingOperation.SUBSCRIPTION_STATUS);
stateStream.open();
let stateStatus = (await stateStatusUpdate)[0];
expect(stateStatus.type).toEqual(mqtt_request_response.SubscriptionStatusEventType.SubscriptionEstablished);
// shouldn't exist yet
await getNonexistentShadow(context.client, thingName, shadowName);
try {
await createShadow(context.client, thingName, shadowName, document);
await getShadow(context.client, thingName, shadowName, document);
let updateDocument = {
color: "blue",
on: false,
};
let deltaEvent = once(deltaEventStream, StreamingOperation.INCOMING_PUBLISH);
let stateEvent = once(stateStream, StreamingOperation.INCOMING_PUBLISH);
await updateShadowDesired(context.client, thingName, shadowName, updateDocument);
let deltaResult = (await deltaEvent)[0];
expect(deltaResult).toBeDefined();
expect(deltaResult.message.state).toEqual(updateDocument);
let stateResult = (await stateEvent)[0];
expect(stateResult).toBeDefined();
expect(stateResult.message.state).toEqual(updateDocument);
await updateShadowReported(context.client, thingName, shadowName, updateDocument);
await getShadow(context.client, thingName, shadowName, updateDocument);
} finally {
deltaEventStream.close();
stateStream.close();
await deleteShadow(context.client, thingName, shadowName, 3);
}
await getNonexistentShadow(context.client, thingName, shadowName);
await context.close();
}
conditional_test(hasTestEnvironment())('shadowv2 - update shadow mqtt5', async () => {
await doUpdateShadowTest(ProtocolVersion.Mqtt5);
});
conditional_test(hasTestEnvironment())('shadowv2 - update shadow mqtt311', async () => {
await doUpdateShadowTest(ProtocolVersion.Mqtt311);
});