initialize testing
parent
74680522c9
commit
285c487cab
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: Coverage Testing (Pull)
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Coverage:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
- name: Install Application
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm install -g mocha c8 cypress@10
|
||||||
|
- name: Copy Test Config
|
||||||
|
run: cp ./data/config.testing.js ./data/config.js
|
||||||
|
- name: Initialize Database
|
||||||
|
run: npm run init:cemetery:test
|
||||||
|
- name: Run Coverage Testing
|
||||||
|
run: c8 --reporter=lcov --reporter=text --reporter=text-summary mocha --timeout 10000 --exit
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: Coverage Testing
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Coverage:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
- name: Install Application
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm install -g mocha c8 cypress@10
|
||||||
|
- name: Copy Test Config
|
||||||
|
run: cp ./data/config.testing.js ./data/config.js
|
||||||
|
- name: Initialize Database
|
||||||
|
run: npm run init:cemetery:test
|
||||||
|
- name: Run Coverage Testing
|
||||||
|
run: c8 --reporter=lcov --reporter=text --reporter=text-summary mocha --timeout 10000 --exit
|
||||||
|
env:
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
- name: Code Climate
|
||||||
|
run: |
|
||||||
|
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./codeclimate-test-reporter
|
||||||
|
chmod +x codeclimate-test-reporter
|
||||||
|
./codeclimate-test-reporter before-build
|
||||||
|
./codeclimate-test-reporter after-build -t lcov --exit-code $?
|
||||||
|
env:
|
||||||
|
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
||||||
|
- name: Codacy
|
||||||
|
run: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r ./coverage/lcov.info
|
||||||
|
env:
|
||||||
|
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
bin/daemon/
|
bin/daemon/
|
||||||
coverage/
|
coverage/
|
||||||
|
cypress/downloads/
|
||||||
|
cypress/screenshots/
|
||||||
|
cypress/videos/
|
||||||
data/sessions/
|
data/sessions/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare const _default: Cypress.ConfigOptions<any>;
|
||||||
|
export default _default;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { defineConfig } from "cypress";
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
"baseUrl": "http://localhost:7000",
|
||||||
|
"specPattern": "cypress/e2e/**/*.cy.ts",
|
||||||
|
"supportFile": false,
|
||||||
|
"projectId": "xya1fn"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
"baseUrl": "http://localhost:7000",
|
||||||
|
"specPattern": "cypress/e2e/**/*.cy.ts",
|
||||||
|
"supportFile": false,
|
||||||
|
"projectId": "xya1fn"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { logout } from "../../support/index.js";
|
||||||
|
describe("Login Page", () => {
|
||||||
|
before(logout);
|
||||||
|
it("Has no detectable accessibility issues", () => {
|
||||||
|
cy.injectAxe();
|
||||||
|
cy.checkA11y();
|
||||||
|
});
|
||||||
|
it("Contains a login form", () => {
|
||||||
|
cy.get("form").should("have.length", 1);
|
||||||
|
});
|
||||||
|
it("Contains a _csrf field", () => {
|
||||||
|
cy.get("form [name='_csrf']").should("exist");
|
||||||
|
});
|
||||||
|
it("Contains a userName field", () => {
|
||||||
|
cy.get("form [name='userName']").should("exist");
|
||||||
|
});
|
||||||
|
it("Contains a password field", () => {
|
||||||
|
cy.get("form [name='password']")
|
||||||
|
.should("have.length", 1)
|
||||||
|
.invoke("attr", "type")
|
||||||
|
.should("equal", "password");
|
||||||
|
});
|
||||||
|
it("Contains a help link", () => {
|
||||||
|
cy.get("a").contains("help", {
|
||||||
|
matchCase: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import {
|
||||||
|
logout
|
||||||
|
} from "../../support/index.js";
|
||||||
|
|
||||||
|
|
||||||
|
describe("Login Page", () => {
|
||||||
|
|
||||||
|
before(logout);
|
||||||
|
|
||||||
|
it("Has no detectable accessibility issues", () => {
|
||||||
|
cy.injectAxe();
|
||||||
|
cy.checkA11y();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Contains a login form", () => {
|
||||||
|
cy.get("form").should("have.length", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Contains a _csrf field", () => {
|
||||||
|
cy.get("form [name='_csrf']").should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Contains a userName field", () => {
|
||||||
|
cy.get("form [name='userName']").should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Contains a password field", () => {
|
||||||
|
cy.get("form [name='password']")
|
||||||
|
.should("have.length", 1)
|
||||||
|
.invoke("attr", "type")
|
||||||
|
.should("equal", "password");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Contains a help link", () => {
|
||||||
|
cy.get("a").contains("help", {
|
||||||
|
matchCase: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import "cypress-axe";
|
||||||
|
export declare const logout: () => void;
|
||||||
|
export declare const login: (userName: string) => void;
|
||||||
|
export declare const ajaxDelayMillis = 800;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import "cypress-axe";
|
||||||
|
Cypress.Cookies.defaults({
|
||||||
|
preserve: ["_csrf", "lot-occupancy-system-user-sid"]
|
||||||
|
});
|
||||||
|
export const logout = () => {
|
||||||
|
cy.visit("/logout");
|
||||||
|
};
|
||||||
|
export const login = (userName) => {
|
||||||
|
cy.visit("/login");
|
||||||
|
cy.get(".message")
|
||||||
|
.contains("Testing", {
|
||||||
|
matchCase: false
|
||||||
|
});
|
||||||
|
cy.get("form [name='userName']").type(userName);
|
||||||
|
cy.get("form [name='password']").type(userName);
|
||||||
|
cy.get("form").submit();
|
||||||
|
cy.location("pathname").should("not.contain", "/login");
|
||||||
|
cy.get(".navbar").should("have.length", 1);
|
||||||
|
};
|
||||||
|
export const ajaxDelayMillis = 800;
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* eslint-disable node/no-unpublished-import */
|
||||||
|
|
||||||
|
import "cypress-axe";
|
||||||
|
|
||||||
|
|
||||||
|
Cypress.Cookies.defaults({
|
||||||
|
preserve: ["_csrf", "lot-occupancy-system-user-sid"]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const logout = (): void => {
|
||||||
|
cy.visit("/logout");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const login = (userName: string): void => {
|
||||||
|
cy.visit("/login");
|
||||||
|
|
||||||
|
cy.get(".message")
|
||||||
|
.contains("Testing", {
|
||||||
|
matchCase: false
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get("form [name='userName']").type(userName);
|
||||||
|
cy.get("form [name='password']").type(userName);
|
||||||
|
|
||||||
|
cy.get("form").submit();
|
||||||
|
|
||||||
|
cy.location("pathname").should("not.contain", "/login");
|
||||||
|
|
||||||
|
// Logged in pages have a navbar
|
||||||
|
cy.get(".navbar").should("have.length", 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const ajaxDelayMillis = 800;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export declare const config: import("../types/configTypes.js").Config;
|
||||||
|
export default config;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { config as cemeteryConfig } from "./config.cemetery.ssm.js";
|
||||||
|
export const config = Object.assign({}, cemeteryConfig);
|
||||||
|
config.application.useTestDatabases = true;
|
||||||
|
config.users = {
|
||||||
|
testing: ["*testView", "*testUpdate", "*testAdmin"],
|
||||||
|
canLogin: ["*testView", "*testUpdate", "*testAdmin"],
|
||||||
|
canUpdate: ["*testUpdate"],
|
||||||
|
isAdmin: ["*testAdmin"]
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { config as cemeteryConfig } from "./config.cemetery.ssm.js";
|
||||||
|
|
||||||
|
export const config = Object.assign({}, cemeteryConfig);
|
||||||
|
|
||||||
|
config.application.useTestDatabases = true;
|
||||||
|
|
||||||
|
config.users = {
|
||||||
|
testing: ["*testView", "*testUpdate", "*testAdmin"],
|
||||||
|
canLogin: ["*testView", "*testUpdate", "*testAdmin"],
|
||||||
|
canUpdate: ["*testUpdate"],
|
||||||
|
isAdmin: ["*testAdmin"]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -14,7 +14,10 @@
|
||||||
"start": "cross-env NODE_ENV=production node ./bin/www",
|
"start": "cross-env NODE_ENV=production node ./bin/www",
|
||||||
"dev:test": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* TEST_DATABASES=true nodemon ./bin/www.js",
|
"dev:test": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* TEST_DATABASES=true nodemon ./bin/www.js",
|
||||||
"dev:live": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* nodemon ./bin/www.js",
|
"dev:live": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* nodemon ./bin/www.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"cy:open": "cypress open --config-file cypress.config.ts",
|
||||||
|
"cy:run": "cypress run --config-file cypress.config.ts",
|
||||||
|
"test": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* TEST_DATABASES=true mocha --timeout 30000 --exit",
|
||||||
|
"coverage": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* TEST_DATABASES=true c8 --reporter=lcov --reporter=text --reporter=text-summary mocha --timeout 30000 --exit",
|
||||||
"temp:legacy:importFromCsv": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* TEST_DATABASES=true node ./temp/legacy.importFromCsv.js",
|
"temp:legacy:importFromCsv": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* TEST_DATABASES=true node ./temp/legacy.importFromCsv.js",
|
||||||
"temp:so:exportMaps": "node ./temp/so.exportMaps.js"
|
"temp:so:exportMaps": "node ./temp/so.exportMaps.js"
|
||||||
},
|
},
|
||||||
|
|
@ -78,6 +81,8 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
||||||
"@typescript-eslint/parser": "^5.34.0",
|
"@typescript-eslint/parser": "^5.34.0",
|
||||||
"bulma": "^0.9.4",
|
"bulma": "^0.9.4",
|
||||||
|
"cypress": "^10.6.0",
|
||||||
|
"cypress-axe": "^1.0.0",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.22.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
|
|
||||||
|
|
@ -128,4 +128,15 @@ fieldset:enabled .is-hidden-enabled {
|
||||||
|
|
||||||
.modal-card {
|
.modal-card {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Accessibility
|
||||||
|
*/
|
||||||
|
|
||||||
|
$black-ter: hsl(0, 0%, 14%);
|
||||||
|
|
||||||
|
.control .button.is-static,
|
||||||
|
.menu .menu-label {
|
||||||
|
color: $black-ter;
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import * as assert from "assert";
|
||||||
|
import { portNumber } from "./_globals.js";
|
||||||
|
import { exec } from "child_process";
|
||||||
|
import * as http from "http";
|
||||||
|
import { app } from "../app.js";
|
||||||
|
describe("lot-occupancy-system", () => {
|
||||||
|
const httpServer = http.createServer(app);
|
||||||
|
let serverStarted = false;
|
||||||
|
before(() => {
|
||||||
|
httpServer.listen(portNumber);
|
||||||
|
httpServer.on("listening", () => {
|
||||||
|
serverStarted = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(() => {
|
||||||
|
try {
|
||||||
|
httpServer.close();
|
||||||
|
}
|
||||||
|
catch (_a) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("Ensure server starts on port " + portNumber.toString(), () => {
|
||||||
|
assert.ok(serverStarted);
|
||||||
|
});
|
||||||
|
describe("Cypress tests", () => {
|
||||||
|
it("should run Cypress tests", (done) => {
|
||||||
|
let cypressCommand = "cypress run --config-file cypress.config.ts --browser chrome";
|
||||||
|
if (process.env.CYPRESS_RECORD_KEY && process.env.CYPRESS_RECORD_KEY !== "") {
|
||||||
|
cypressCommand += " --record";
|
||||||
|
}
|
||||||
|
const childProcess = exec(cypressCommand);
|
||||||
|
childProcess.stdout.on("data", (data) => {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
childProcess.stderr.on("data", (data) => {
|
||||||
|
console.error(data);
|
||||||
|
});
|
||||||
|
childProcess.on("exit", (code) => {
|
||||||
|
assert.ok(code === 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}).timeout(30 * 60 * 60 * 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* eslint-disable unicorn/filename-case */
|
||||||
|
|
||||||
|
import * as assert from "assert";
|
||||||
|
|
||||||
|
import {
|
||||||
|
portNumber
|
||||||
|
} from "./_globals.js";
|
||||||
|
|
||||||
|
import {
|
||||||
|
exec
|
||||||
|
} from "child_process";
|
||||||
|
|
||||||
|
import * as http from "http";
|
||||||
|
import {
|
||||||
|
app
|
||||||
|
} from "../app.js";
|
||||||
|
|
||||||
|
|
||||||
|
describe("lot-occupancy-system", () => {
|
||||||
|
|
||||||
|
const httpServer = http.createServer(app);
|
||||||
|
|
||||||
|
let serverStarted = false;
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
|
||||||
|
httpServer.listen(portNumber);
|
||||||
|
|
||||||
|
httpServer.on("listening", () => {
|
||||||
|
serverStarted = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
try {
|
||||||
|
httpServer.close();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Ensure server starts on port " + portNumber.toString(), () => {
|
||||||
|
assert.ok(serverStarted);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Cypress tests", () => {
|
||||||
|
|
||||||
|
it("should run Cypress tests", (done) => {
|
||||||
|
|
||||||
|
let cypressCommand = "cypress run --config-file cypress.config.ts --browser chrome";
|
||||||
|
|
||||||
|
if (process.env.CYPRESS_RECORD_KEY && process.env.CYPRESS_RECORD_KEY !== "") {
|
||||||
|
cypressCommand += " --record";
|
||||||
|
}
|
||||||
|
|
||||||
|
const childProcess = exec(cypressCommand);
|
||||||
|
|
||||||
|
childProcess.stdout.on("data", (data) => {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.stderr.on("data", (data) => {
|
||||||
|
console.error(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.on("exit", (code) => {
|
||||||
|
assert.ok(code === 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}).timeout(30 * 60 * 60 * 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/// <reference types="qs" />
|
||||||
|
import type { Request } from "express";
|
||||||
|
import type { Session } from "express-session";
|
||||||
|
export declare const testView = "*testView";
|
||||||
|
export declare const testUpdate = "*testUpdate";
|
||||||
|
export declare const testAdmin = "*testAdmin";
|
||||||
|
export declare const portNumber = 7000;
|
||||||
|
export declare const fakeViewOnlySession: Session;
|
||||||
|
export declare const fakeAdminSession: Session;
|
||||||
|
export declare const fakeRequest: Request;
|
||||||
|
export declare const fakeViewOnlyRequest: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>> & {
|
||||||
|
session: Session;
|
||||||
|
};
|
||||||
|
export declare const fakeAdminRequest: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>> & {
|
||||||
|
session: Session;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
||||||
|
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
||||||
|
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||||
|
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||||
|
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||||
|
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||||||
|
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||||
|
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||||
|
function fulfill(value) { resume("next", value); }
|
||||||
|
function reject(value) { resume("throw", value); }
|
||||||
|
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||||
|
};
|
||||||
|
export const testView = "*testView";
|
||||||
|
export const testUpdate = "*testUpdate";
|
||||||
|
export const testAdmin = "*testAdmin";
|
||||||
|
export const portNumber = 7000;
|
||||||
|
export const fakeViewOnlySession = {
|
||||||
|
id: "",
|
||||||
|
cookie: undefined,
|
||||||
|
destroy: undefined,
|
||||||
|
regenerate: undefined,
|
||||||
|
reload: undefined,
|
||||||
|
resetMaxAge: undefined,
|
||||||
|
save: undefined,
|
||||||
|
touch: undefined,
|
||||||
|
user: undefined
|
||||||
|
};
|
||||||
|
export const fakeAdminSession = {
|
||||||
|
id: "",
|
||||||
|
cookie: undefined,
|
||||||
|
destroy: undefined,
|
||||||
|
regenerate: undefined,
|
||||||
|
reload: undefined,
|
||||||
|
resetMaxAge: undefined,
|
||||||
|
save: undefined,
|
||||||
|
touch: undefined,
|
||||||
|
user: undefined
|
||||||
|
};
|
||||||
|
export const fakeRequest = {
|
||||||
|
[Symbol.asyncIterator]() { return __asyncGenerator(this, arguments, function* _a() { }); },
|
||||||
|
_destroy: undefined,
|
||||||
|
_read: undefined,
|
||||||
|
aborted: undefined,
|
||||||
|
accepted: undefined,
|
||||||
|
accepts: undefined,
|
||||||
|
acceptsCharsets: undefined,
|
||||||
|
acceptsEncodings: undefined,
|
||||||
|
acceptsLanguages: undefined,
|
||||||
|
addListener: undefined,
|
||||||
|
app: undefined,
|
||||||
|
baseUrl: undefined,
|
||||||
|
body: undefined,
|
||||||
|
cookies: undefined,
|
||||||
|
complete: undefined,
|
||||||
|
connection: undefined,
|
||||||
|
csrfToken: undefined,
|
||||||
|
destroy: undefined,
|
||||||
|
destroyed: undefined,
|
||||||
|
emit: undefined,
|
||||||
|
eventNames: undefined,
|
||||||
|
fresh: undefined,
|
||||||
|
get: undefined,
|
||||||
|
getMaxListeners: undefined,
|
||||||
|
header: undefined,
|
||||||
|
headers: undefined,
|
||||||
|
host: undefined,
|
||||||
|
hostname: undefined,
|
||||||
|
httpVersion: undefined,
|
||||||
|
httpVersionMajor: undefined,
|
||||||
|
httpVersionMinor: undefined,
|
||||||
|
ip: undefined,
|
||||||
|
ips: undefined,
|
||||||
|
is: undefined,
|
||||||
|
isPaused: undefined,
|
||||||
|
listenerCount: undefined,
|
||||||
|
listeners: undefined,
|
||||||
|
method: undefined,
|
||||||
|
off: undefined,
|
||||||
|
on: undefined,
|
||||||
|
once: undefined,
|
||||||
|
originalUrl: undefined,
|
||||||
|
param: undefined,
|
||||||
|
params: undefined,
|
||||||
|
path: undefined,
|
||||||
|
pause: undefined,
|
||||||
|
pipe: undefined,
|
||||||
|
prependListener: undefined,
|
||||||
|
prependOnceListener: undefined,
|
||||||
|
protocol: undefined,
|
||||||
|
push: undefined,
|
||||||
|
query: undefined,
|
||||||
|
range: undefined,
|
||||||
|
rawHeaders: undefined,
|
||||||
|
rawListeners: undefined,
|
||||||
|
rawTrailers: undefined,
|
||||||
|
read: undefined,
|
||||||
|
readable: undefined,
|
||||||
|
readableAborted: undefined,
|
||||||
|
readableDidRead: undefined,
|
||||||
|
readableEncoding: undefined,
|
||||||
|
readableEnded: undefined,
|
||||||
|
readableFlowing: undefined,
|
||||||
|
readableLength: undefined,
|
||||||
|
readableHighWaterMark: undefined,
|
||||||
|
readableObjectMode: undefined,
|
||||||
|
removeAllListeners: undefined,
|
||||||
|
removeListener: undefined,
|
||||||
|
resume: undefined,
|
||||||
|
route: undefined,
|
||||||
|
secure: undefined,
|
||||||
|
session: undefined,
|
||||||
|
sessionID: undefined,
|
||||||
|
sessionStore: undefined,
|
||||||
|
setEncoding: undefined,
|
||||||
|
setMaxListeners: undefined,
|
||||||
|
setTimeout: undefined,
|
||||||
|
signedCookies: undefined,
|
||||||
|
socket: undefined,
|
||||||
|
stale: undefined,
|
||||||
|
subdomains: undefined,
|
||||||
|
trailers: undefined,
|
||||||
|
unpipe: undefined,
|
||||||
|
unshift: undefined,
|
||||||
|
url: undefined,
|
||||||
|
wrap: undefined,
|
||||||
|
xhr: undefined
|
||||||
|
};
|
||||||
|
export const fakeViewOnlyRequest = Object.assign({}, fakeRequest, {
|
||||||
|
session: fakeViewOnlySession
|
||||||
|
});
|
||||||
|
export const fakeAdminRequest = Object.assign({}, fakeRequest, {
|
||||||
|
session: fakeAdminSession
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
import type {
|
||||||
|
Request
|
||||||
|
} from "express";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Session
|
||||||
|
} from "express-session";
|
||||||
|
|
||||||
|
|
||||||
|
export const testView = "*testView";
|
||||||
|
export const testUpdate = "*testUpdate";
|
||||||
|
export const testAdmin = "*testAdmin";
|
||||||
|
|
||||||
|
|
||||||
|
export const portNumber = 7000;
|
||||||
|
|
||||||
|
|
||||||
|
export const fakeViewOnlySession: Session = {
|
||||||
|
id: "",
|
||||||
|
cookie: undefined,
|
||||||
|
destroy: undefined,
|
||||||
|
regenerate: undefined,
|
||||||
|
reload: undefined,
|
||||||
|
resetMaxAge: undefined,
|
||||||
|
save: undefined,
|
||||||
|
touch: undefined,
|
||||||
|
user: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const fakeAdminSession: Session = {
|
||||||
|
id: "",
|
||||||
|
cookie: undefined,
|
||||||
|
destroy: undefined,
|
||||||
|
regenerate: undefined,
|
||||||
|
reload: undefined,
|
||||||
|
resetMaxAge: undefined,
|
||||||
|
save: undefined,
|
||||||
|
touch: undefined,
|
||||||
|
user: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const fakeRequest: Request = {
|
||||||
|
async *[Symbol.asyncIterator]() {},
|
||||||
|
_destroy: undefined,
|
||||||
|
_read: undefined,
|
||||||
|
aborted: undefined,
|
||||||
|
accepted: undefined,
|
||||||
|
accepts: undefined,
|
||||||
|
acceptsCharsets: undefined,
|
||||||
|
acceptsEncodings: undefined,
|
||||||
|
acceptsLanguages: undefined,
|
||||||
|
addListener: undefined,
|
||||||
|
app: undefined,
|
||||||
|
baseUrl: undefined,
|
||||||
|
body: undefined,
|
||||||
|
cookies: undefined,
|
||||||
|
complete: undefined,
|
||||||
|
connection: undefined,
|
||||||
|
csrfToken: undefined,
|
||||||
|
destroy: undefined,
|
||||||
|
destroyed: undefined,
|
||||||
|
emit: undefined,
|
||||||
|
eventNames: undefined,
|
||||||
|
fresh: undefined,
|
||||||
|
get: undefined,
|
||||||
|
getMaxListeners: undefined,
|
||||||
|
header: undefined,
|
||||||
|
headers: undefined,
|
||||||
|
host: undefined,
|
||||||
|
hostname: undefined,
|
||||||
|
httpVersion: undefined,
|
||||||
|
httpVersionMajor: undefined,
|
||||||
|
httpVersionMinor: undefined,
|
||||||
|
ip: undefined,
|
||||||
|
ips: undefined,
|
||||||
|
is: undefined,
|
||||||
|
isPaused: undefined,
|
||||||
|
listenerCount: undefined,
|
||||||
|
listeners: undefined,
|
||||||
|
method: undefined,
|
||||||
|
off: undefined,
|
||||||
|
on: undefined,
|
||||||
|
once: undefined,
|
||||||
|
originalUrl: undefined,
|
||||||
|
param: undefined,
|
||||||
|
params: undefined,
|
||||||
|
path: undefined,
|
||||||
|
pause: undefined,
|
||||||
|
pipe: undefined,
|
||||||
|
prependListener: undefined,
|
||||||
|
prependOnceListener: undefined,
|
||||||
|
protocol: undefined,
|
||||||
|
push: undefined,
|
||||||
|
query: undefined,
|
||||||
|
range: undefined,
|
||||||
|
rawHeaders: undefined,
|
||||||
|
rawListeners: undefined,
|
||||||
|
rawTrailers: undefined,
|
||||||
|
read: undefined,
|
||||||
|
readable: undefined,
|
||||||
|
readableAborted: undefined,
|
||||||
|
readableDidRead: undefined,
|
||||||
|
readableEncoding: undefined,
|
||||||
|
readableEnded: undefined,
|
||||||
|
readableFlowing: undefined,
|
||||||
|
readableLength: undefined,
|
||||||
|
readableHighWaterMark: undefined,
|
||||||
|
readableObjectMode: undefined,
|
||||||
|
removeAllListeners: undefined,
|
||||||
|
removeListener: undefined,
|
||||||
|
resume: undefined,
|
||||||
|
route: undefined,
|
||||||
|
secure: undefined,
|
||||||
|
session: undefined,
|
||||||
|
sessionID: undefined,
|
||||||
|
sessionStore: undefined,
|
||||||
|
setEncoding: undefined,
|
||||||
|
setMaxListeners: undefined,
|
||||||
|
setTimeout: undefined,
|
||||||
|
signedCookies: undefined,
|
||||||
|
socket: undefined,
|
||||||
|
stale: undefined,
|
||||||
|
subdomains: undefined,
|
||||||
|
trailers: undefined,
|
||||||
|
unpipe: undefined,
|
||||||
|
unshift: undefined,
|
||||||
|
url: undefined,
|
||||||
|
wrap: undefined,
|
||||||
|
xhr: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const fakeViewOnlyRequest =
|
||||||
|
Object.assign({}, fakeRequest, {
|
||||||
|
session: fakeViewOnlySession
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const fakeAdminRequest =
|
||||||
|
Object.assign({}, fakeRequest, {
|
||||||
|
session: fakeAdminSession
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import * as assert from "assert";
|
||||||
|
import fs from "fs";
|
||||||
|
import { version } from "../version.js";
|
||||||
|
describe("version", () => {
|
||||||
|
it("has a version that matches the package.json", () => {
|
||||||
|
const packageJSON = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||||
|
assert.strictEqual(version, packageJSON.version);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import * as assert from "assert";
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
version
|
||||||
|
} from "../version.js";
|
||||||
|
|
||||||
|
|
||||||
|
describe("version", () => {
|
||||||
|
|
||||||
|
it("has a version that matches the package.json", () => {
|
||||||
|
const packageJSON = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||||
|
assert.strictEqual(version, packageJSON.version);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -42,6 +42,7 @@ interface ConfigApplication {
|
||||||
logoURL?: string;
|
logoURL?: string;
|
||||||
httpPort?: number;
|
httpPort?: number;
|
||||||
userDomain?: string;
|
userDomain?: string;
|
||||||
|
useTestDatabases?: boolean;
|
||||||
}
|
}
|
||||||
interface ConfigSession {
|
interface ConfigSession {
|
||||||
cookieName?: string;
|
cookieName?: string;
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ interface ConfigApplication {
|
||||||
logoURL ? : string;
|
logoURL ? : string;
|
||||||
httpPort ? : number;
|
httpPort ? : number;
|
||||||
userDomain ? : string;
|
userDomain ? : string;
|
||||||
|
useTestDatabases ?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
134
views/login.ejs
134
views/login.ejs
|
|
@ -15,75 +15,75 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="columns is-vcentered is-centered has-min-page-height is-marginless">
|
<div class="columns is-vcentered is-centered has-min-page-height is-marginless">
|
||||||
<div class="column is-half-widescreen is-two-thirds-desktop is-three-quarters-tablet">
|
<div class="column is-half-widescreen is-two-thirds-desktop is-three-quarters-tablet">
|
||||||
<div class="box mx-3 my-3">
|
<main class="box mx-3 my-3">
|
||||||
<div class="columns is-vcentered">
|
<div class="columns is-vcentered">
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered">
|
||||||
<img src="<%= urlPrefix + configFunctions.getProperty("application.logoURL") %>" alt="" style="max-height:400px" />
|
<img src="<%= urlPrefix + configFunctions.getProperty("application.logoURL") %>" alt="" style="max-height:400px" />
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<h1 class="title is-3 has-text-centered">
|
|
||||||
<%= configFunctions.getProperty("application.applicationName") %>
|
|
||||||
</h1>
|
|
||||||
<form id="form--login" method="post" action="<%= urlPrefix %>/login">
|
|
||||||
<input name="_csrf" type="hidden" value="<%= csrfToken %>" />
|
|
||||||
<input name="redirect" type="hidden" value="<%= redirect %>" />
|
|
||||||
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control">
|
|
||||||
<span class="button is-static"><%= configFunctions.getProperty("application.userDomain") %>\</span>
|
|
||||||
</div>
|
|
||||||
<div class="control is-expanded">
|
|
||||||
<input class="input" id="login--userName" name="userName" type="text" placeholder="User Name" value="<%= userName %>" aria-label="User Name" autofocus required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="sr-only" for="login--password">Password</label>
|
|
||||||
<div class="control has-icons-left has-tooltip-right" data-tooltip="Password" >
|
|
||||||
<input class="input" id="login--password" name="password" type="password" placeholder="Password" required />
|
|
||||||
<span class="icon is-small is-left">
|
|
||||||
<i class="fas fa-key" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% if (useTestDatabases) { %>
|
|
||||||
<div class="message is-small is-warning">
|
|
||||||
<p class="message-body has-text-centered">
|
|
||||||
Testing databases in use!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
<div class="level is-mobile">
|
|
||||||
<div class="level-left has-text-danger">
|
|
||||||
<% if (message !== "") { %>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
<span><%= message %></span>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
<div class="level-right has-text-right">
|
|
||||||
<button class="button is-link" type="submit">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
<span>Log In</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<hr />
|
|
||||||
<div class="has-text-right has-text-grey is-size-7">
|
|
||||||
Build <%= buildNumber %><br />
|
|
||||||
<a class="has-text-grey" href="https://cityssm.github.io/general-licence-manager/" target="_blank" rel="nofollow noreferrer">Help</a>
|
|
||||||
<a class="has-text-grey ml-4" href="https://github.com/cityssm/general-licence-manager" target="_blank" rel="noreferrer">GitHub</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="column">
|
||||||
|
<h1 class="title is-3 has-text-centered">
|
||||||
|
<%= configFunctions.getProperty("application.applicationName") %>
|
||||||
|
</h1>
|
||||||
|
<form id="form--login" method="post" action="<%= urlPrefix %>/login">
|
||||||
|
<input name="_csrf" type="hidden" value="<%= csrfToken %>" />
|
||||||
|
<input name="redirect" type="hidden" value="<%= redirect %>" />
|
||||||
|
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control">
|
||||||
|
<span class="button is-static"><%= configFunctions.getProperty("application.userDomain") %>\</span>
|
||||||
|
</div>
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<input class="input" id="login--userName" name="userName" type="text" placeholder="User Name" value="<%= userName %>" aria-label="User Name" autofocus required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="sr-only" for="login--password">Password</label>
|
||||||
|
<div class="control has-icons-left has-tooltip-right" data-tooltip="Password" >
|
||||||
|
<input class="input" id="login--password" name="password" type="password" placeholder="Password" required />
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-key" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% if (useTestDatabases) { %>
|
||||||
|
<div class="message is-small is-warning">
|
||||||
|
<p class="message-body has-text-centered">
|
||||||
|
Testing databases in use!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
<div class="level is-mobile">
|
||||||
|
<div class="level-left has-text-danger">
|
||||||
|
<% if (message !== "") { %>
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
<span><%= message %></span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<div class="level-right has-text-right">
|
||||||
|
<button class="button is-link" type="submit">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
<span>Log In</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<hr />
|
||||||
|
<div class="has-text-right has-text-grey-dark is-size-7">
|
||||||
|
Build <%= buildNumber %><br />
|
||||||
|
<a class="has-text-grey-dark" href="https://cityssm.github.io/general-licence-manager/" target="_blank" rel="nofollow noreferrer">Help</a>
|
||||||
|
<a class="has-text-grey-dark ml-4" href="https://github.com/cityssm/general-licence-manager" target="_blank" rel="noreferrer">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
try {
|
try {
|
||||||
|
|
@ -92,4 +92,4 @@
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in New Issue