Skip to content

Commit

Permalink
Added automated end to end test to verify deployed components
Browse files Browse the repository at this point in the history
* .gitignore Updated to ignore node modules
* iothubs.bicep updated with lower default sku
* README.md updated with "Automated End-to-End Test" commands
* e2e/ NodeJs end to end test added
* test/ Data files updated with more appropriate deviceId
  • Loading branch information
jmostella committed Dec 29, 2020
1 parent fd1c8e2 commit cbe14bc
Show file tree
Hide file tree
Showing 10 changed files with 2,207 additions and 14 deletions.
1 change: 1 addition & 0 deletions single_tech_samples/streamanalytics/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
output
testResultSummary.json
e2e/node_modules
18 changes: 9 additions & 9 deletions single_tech_samples/streamanalytics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,18 @@ az iot hub device-identity connection-string show --hub-name iot-tech-sample-tes

__Check the blob storage container to ensure that a json file exists with expected temperature sensor data (exceeding 27 degrees) is present__

## Automated End-to-End Test

```bash
export DEVICE_CONNECTION_STRING=$(az iot hub device-identity connection-string show --hub-name iot-tech-sample-test --device-id iot-tech-sample-test --output tsv)
export AZURE_STORAGE_CONNECTION_STRING=$(az storage account show-connection-string -n sttechsampletest --query connectionString -o tsv)

npm test --prefix e2e/
```

## Key concepts

`WIP`

__IaC__ Resource naming conventions attempt to follow examples from [Define your naming convention
](https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming#example-names-for-common-azure-resource-types) where appropriate.



---

```bash
# install npm pkg for https://docs.microsoft.com/en-us/azure/stream-analytics/cicd-tools?tabs=visual-studio-code
npm install -g azure-streamanalytics-cicd
```
140 changes: 140 additions & 0 deletions single_tech_samples/streamanalytics/e2e/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import * as chai from 'chai';
import { Mqtt } from 'azure-iot-device-mqtt';
import { Client, Message } from 'azure-iot-device';
import { BlobItem, ContainerClient } from '@azure/storage-blob';
chai.use(require('chai-subset'));

const EVENT_SINK_CONTAINER = 'bloboutput';
const EXPECTED_LATENCY_MS = 7200;

describe('Send to IoT Hub', () => {
let iotClient: Client;
let containerClient: ContainerClient;

before(() => {
chai.expect(process.env.DEVICE_CONNECTION_STRING).to.be.not.empty;
chai.expect(process.env.AZURE_STORAGE_CONNECTION_STRING).to.be.not.empty;
});

before(async () => {
iotClient = Client.fromConnectionString(process.env.DEVICE_CONNECTION_STRING, Mqtt);
await iotClient.open();
});

before(() => {
containerClient = new ContainerClient(process.env.AZURE_STORAGE_CONNECTION_STRING, EVENT_SINK_CONTAINER);
});

before(async () => {
await deleteAllBlobs();
});

after(async function () {
await iotClient.close();
});

afterEach(async () => {
await deleteAllBlobs();
});

async function send(message: Message) {
await iotClient.sendEvent(message);
};

async function deleteAllBlobs() {
for await (const blob of containerClient.listBlobsFlat()) {
await containerClient.deleteBlob(blob.name);
}
}

async function getFirstBlob(): Promise<BlobItem> {
for await (const blob of containerClient.listBlobsFlat()) {
return blob;
}
}

async function getBlobData(blob: BlobItem): Promise<string> {
const client = containerClient.getBlockBlobClient(blob.name);
const response = await client.download();
return (await streamToBuffer(response.readableStreamBody!)).toString();
}

function convertBlobData(blobData: string): any[] {
return blobData.split('\n').map(entry => JSON.parse(entry));
}

// A helper method used to read a Node.js readable stream into a Buffer
async function streamToBuffer(readableStream: NodeJS.ReadableStream): Promise<Buffer> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
readableStream.on("data", (data: Buffer | string) => {
chunks.push(data instanceof Buffer ? data : Buffer.from(data));
});
readableStream.on("end", () => {
resolve(Buffer.concat(chunks));
});
readableStream.on("error", reject);
});
}

async function delay(factor = 1) {
await new Promise(resolve => setTimeout(resolve, EXPECTED_LATENCY_MS * factor));
}

describe('payload with temperature', () => {
describe('greater than 27 degrees', () => {
it('should contain entry in blob', async () => {
const data = {
deviceId: 'modern-data-warehouse-dataops/single_tech_samples/streamanalytics/e2e',
temperature: 27.1
};
const message = new Message(JSON.stringify(data));

await send(message);

await delay();

const blob = await getFirstBlob();
chai.expect(blob).to.not.be.undefined;
const blobData = await getBlobData(blob);
const entries = convertBlobData(blobData);
chai.expect(entries).to.have.length(1);
chai.expect(entries).to.containSubset([data]);
});
});

describe('equal to 27 degrees', () => {
it('should not contain entry in blob', async () => {
const data = {
deviceId: 'modern-data-warehouse-dataops/single_tech_samples/streamanalytics/e2e',
temperature: 27
};
const message = new Message(JSON.stringify(data));

await send(message);

await delay(2);

const blob = await getFirstBlob();
chai.expect(blob).to.be.undefined;
});
});

describe('less than 27 degrees', () => {
it('should not contain entry in blob', async () => {
const data = {
deviceId: 'modern-data-warehouse-dataops/single_tech_samples/streamanalytics/e2e',
temperature: 26.9
};
const message = new Message(JSON.stringify(data));

await send(message);

await delay(2);

const blob = await getFirstBlob();
chai.expect(blob).to.be.undefined;
});
});
});
});
Loading

0 comments on commit cbe14bc

Please sign in to comment.