Skip to content
This repository has been archived by the owner on Aug 9, 2021. It is now read-only.

Commit

Permalink
Added Cypress tests (#348)
Browse files Browse the repository at this point in the history
* added Cypress tests

* Now all typescripted

* Improved test to be more clear what is testing when.

* Adding logging to help clarrify tests.

* First recording will upload

* Refactored recording upload

* Visits now checked, improved test in runner still timing issues.

* First visit tests are complete.

* Fixes after Jons review

* Beautify run

* Travis and readme updated

* Travis again.

* Excluding cypress ts from api ts build.

* Single eslint file.

* Eslint uses local packages

* Fixed bug with custom tracks not being added

Co-authored-by: Clare McLennan <>
  • Loading branch information
clare authored Feb 23, 2021
1 parent b82deb1 commit 9aff968
Show file tree
Hide file tree
Showing 30 changed files with 3,289 additions and 4 deletions.
9 changes: 8 additions & 1 deletion .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ env:
es6: true
parserOptions:
ecmaVersion: 8
extends: 'eslint:recommended'
plugins:
- 'cypress'
- 'no-only-tests'
extends:
- 'eslint:recommended'
- 'plugin:cypress/recommended'
rules:
indent:
- error
Expand All @@ -26,3 +31,5 @@ rules:
- error
prefer-const:
- error
no-only-tests/no-only-tests:
- error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ api/**/*.js
models/**/*.js
/*.js
dockerstart.log
test-cypress/cypress.json
9 changes: 8 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ python:
install:
- npm install -g [email protected]
- npm run apidoc
- npm install -g eslint
- npm install -g eslint
# global eslint uses local plugins
- npm install eslint-plugin-cypress eslint-plugin-no-only-tests
- sudo apt update
- sudo apt install python3-pip
- sudo apt-get install python3-setuptools
Expand All @@ -34,6 +36,11 @@ script:
- python3 -m pytest --log-api-on-fail
- cd ..

- cd test-cypress
- cp cypress.json.TEMPLATE cypress.json
- npm install
- npm run release

before_deploy:
- version=${TRAVIS_TAG/v/}
# Install nfpm tool (for building debs)
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ To start psql to query the database in base use the alias `psqltest`

### Running the tests

The Cacophony API server has two functional test suites.

The current tests are currently being rewritten using Cypress (typescript).
There is also a legacy set of test in python.

Currently both sets of tests need to be run.

#### Running the cypress tests

See [Cypress Tests](test-cypress/README.md) for details on running the cypress tests.

#### Running the python tests

The Cacophony API server has a comprehensive function test suite. This
requires Python 3.

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
"winston": "^1.0.1"
},
"devDependencies": {
"eslint": "^5.16.0",
"eslint": "^6.8.0",
"eslint-plugin-cypress": "^2.7.0",
"eslint-plugin-no-only-tests": "^2.4.0",
"eslint-utils": "^2.0.0",
"prettier": "^2.0.5",
"tsc-watch": "^4.2.3",
Expand Down
16 changes: 16 additions & 0 deletions test-cypress/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Cypress api tests

## To run the tests on your own machine
To set up the tests on your own machine:
1. Go to the test-cypress folder
2. Copy cypress.json.TEMPLATE to cypress.json.
3. Run
``` bash
npm install
npm run dev
```
4. Look for the [cypress](https://www.cypress.io/) interactive environment.

### Testing against a different server
You can test against code running on any environment by changing the cacophony-api-server in cypress.json

7 changes: 7 additions & 0 deletions test-cypress/cypress.json.TEMPLATE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"projectId": "dyez6t",
"env" : {
"cacophony-api-server": "http://localhost:1080",
"testCreds" : {}
}
}
23 changes: 23 additions & 0 deletions test-cypress/cypress/commands/api/camera.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// load the global Cypress types
/// <reference types="cypress" />

declare namespace Cypress {
interface Chainable {
/**
* create a group for the given user (who has already been referenced in the test
*/
apiCreateCamera(
cameraName: string,
group: string,
log?: boolean
): Chainable<Element>;

/**
* use to test when a camera should not be able to be created.
*/
apiShouldFailToCreateCamera(
cameraName: string,
group: string
): Chainable<Element>;
}
}
61 changes: 61 additions & 0 deletions test-cypress/cypress/commands/api/camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getTestName } from "../names";
import { v1ApiPath, saveCreds, checkRequestFails } from "../server";
import { logTestDescription } from "../descriptions";

Cypress.Commands.add(
"apiCreateCamera",
(cameraName: string, group: string, log = true) => {
logTestDescription(
`Create camera '${cameraName}' in group '${group}'`,
{
camera: cameraName,
group: group
},
log
);

const request = createCameraDetails(cameraName, group);
cy.request(request).then((response) => {
const id = response.body.id;
saveCreds(response, cameraName, id);
});
}
);

Cypress.Commands.add(
"apiShouldFailToCreateCamera",
(cameraName: string, group: string, log = true) => {
logTestDescription(
`Check that user cannot create camera '${cameraName}' in group '${group}'`,
{
camera: cameraName,
group: group
},
log
);

const request = createCameraDetails(cameraName, group);
checkRequestFails(request);
}
);

function createCameraDetails(
cameraName: string,
group: string,
shouldFail = false
): any {
const fullName = getTestName(cameraName);
const password = "p" + fullName;

const data = {
devicename: fullName,
password: password,
group: getTestName(group)
};

return {
method: "POST",
url: v1ApiPath("devices"),
body: data
};
}
32 changes: 32 additions & 0 deletions test-cypress/cypress/commands/api/recording.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// load the global Cypress types
/// <reference types="cypress" />

interface TrackInfo {
// start_s? : 10,
// end_s? : 22.2,
tag?: string;
// confidence?: number,
}

interface ThermalRecordingInfo {
time?: Date;
duration?: number;
model?: string;
tracks?: TrackInfo[];
noTracks?: boolean; // by default there will normally be one track, set to true if you don't want tracks
minsLater?: number; // minutes that later that the recording is taken
secsLater?: number; // minutes that later that the recording is taken
}

declare namespace Cypress {
interface Chainable {
/**
* upload a single recording to for a particular camera
*/
uploadRecording(
cameraName: string,
details: ThermalRecordingInfo,
log?: boolean
): Chainable<Element>;
}
}
139 changes: 139 additions & 0 deletions test-cypress/cypress/commands/api/recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// load the global Cypress types
/// <reference types="cypress" />

import { v1ApiPath, uploadFile, DEFAULT_DATE } from "../server";
import { logTestDescription } from "../descriptions";

let lastUsedTime = DEFAULT_DATE;

Cypress.Commands.add(
"uploadRecording",
(cameraName: string, details: ThermalRecordingInfo, log = true) => {
const data = makeRecordingDataFromDetails(details);

logTestDescription(
`Upload '${JSON.stringify(details)}' recording to '${cameraName}'`,
{ camera: cameraName, requestData: data },
log
);

const fileName = "invalid.cptv";
const url = v1ApiPath("recordings");
const fileType = "application/cptv";

uploadFile(url, cameraName, fileName, fileType, data);
// must wait until the upload request has completed
cy.wait(["@addRecording"]);
}
);

type IsoFormattedDateString = string;

interface TrackData {
start_s?: number;
end_s?: number;
confident_tag?: string;
confidence?: number;
}

interface AlgorithmMetadata {
model_name?: string;
}

interface ThermalRecordingMetaData {
algorithm?: AlgorithmMetadata;
tracks: TrackData[];
}

interface ThermalRecordingData {
type: "thermalRaw";
recordingDateTime: IsoFormattedDateString;
duration: number;
comment?: string;
batteryLevel?: number;
batteryCharging?: string;
airplaneModeOn?: boolean;
version?: string;
additionalMetadata?: JSON;
metadata?: ThermalRecordingMetaData;
}

function makeRecordingDataFromDetails(
details: ThermalRecordingInfo
): ThermalRecordingData {
let data: ThermalRecordingData = {
type: "thermalRaw",
recordingDateTime: "",
duration: 12,
comment: "uploaded by cypress"
};

if (details.duration) {
data.duration = details.duration;
}

data.recordingDateTime = getDateForRecordings(details).toISOString();

if (!details.noTracks) {
const model = details.model ? details.model : "Master";
addTracksToRecording(data, model, details.tracks);
}

return data;
}

function getDateForRecordings(details: ThermalRecordingInfo): Date {
let date = lastUsedTime;

if (details.time) {
date = details.time;
} else if (details.minsLater || details.secsLater) {
let secs = 0;
if (details.minsLater) {
secs += details.minsLater * 60;
}
if (details.secsLater) {
secs += details.secsLater;
}
date = new Date(date.getTime() + secs * 1000);
} else {
// add a minute anyway so we don't get two overlapping recordings on the same camera
const MINUTE = 60;
date = new Date(date.getTime() + MINUTE * 1000);
}

lastUsedTime = date;
return date;
}

function addTracksToRecording(
data: ThermalRecordingData,
model: string,
trackDetails?: TrackInfo[]
): void {
data.metadata = {
algorithm: {
model_name: model
},
tracks: []
};

if (trackDetails) {
data.metadata.tracks = trackDetails.map((track) => {
let tag = track.tag ? track.tag : "possum";
return {
start_s: 2,
end_s: 8,
confident_tag: tag,
confidence: 0.9
};
});
} else {
data.metadata.tracks.push({
start_s: 2,
end_s: 8,
confident_tag: "possum",
confidence: 0.5
});
}
}
Loading

0 comments on commit 9aff968

Please sign in to comment.