Converted tests to typescript. (#6181)

* Converted tests to typescript.

* Run all tests.

* Fixed tests.

* Removed cypress from every installation.

* Use cache for libreoffice.

* Fixed cypress install.

* Fixed cypress install.
This commit is contained in:
SamTV12345 2024-02-22 18:31:17 +01:00 committed by GitHub
parent 4bd27a1c79
commit 546ede284c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 912 additions and 734 deletions

View file

@ -1,48 +0,0 @@
'use strict';
// support for older node versions (<12)
const assert = require('assert');
const internalMatch = (string, regexp, message, fn) => {
if (!regexp.test) {
throw new Error('regexp parameter is not a RegExp');
}
if (typeof string !== 'string') {
throw new Error('string parameter is not a string');
}
const match = fn.name === 'match';
const result = string.match(regexp);
if (match && !result) {
if (message) {
throw message;
} else {
throw new Error(`${string} does not match regex ${regexp}`);
}
}
if (!match && result) {
if (message) {
throw message;
} else {
throw new Error(`${string} does match regex ${regexp}`);
}
}
};
if (!assert.match) {
const match = (string, regexp, message) => {
internalMatch(string, regexp, message, match);
};
assert.match = match;
}
if (!assert.strict.match) assert.strict.match = assert.match;
if (!assert.doesNotMatch) {
const doesNotMatch = (string, regexp, message) => {
internalMatch(string, regexp, message, doesNotMatch);
};
assert.doesNotMatch = doesNotMatch;
}
if (!assert.strict.doesNotMatch) assert.strict.doesNotMatch = assert.doesNotMatch;
module.exports = assert;

View file

@ -1,5 +1,7 @@
'use strict';
import {MapArrayType} from "../../node/types/MapType";
const AttributePool = require('../../static/js/AttributePool');
const apiHandler = require('../../node/handler/APIHandler');
const assert = require('assert').strict;
@ -10,11 +12,11 @@ const process = require('process');
const server = require('../../node/server');
const setCookieParser = require('set-cookie-parser');
const settings = require('../../node/utils/Settings');
const supertest = require('supertest');
import supertest from 'supertest';
const webaccess = require('../../node/hooks/express/webaccess');
const backups = {};
let agentPromise = null;
const backups:MapArrayType<any> = {};
let agentPromise:Promise<any>|null = null;
exports.apiKey = apiHandler.exportedForTestingOnly.apiKey;
exports.agent = null;
@ -27,7 +29,7 @@ const logLevel = logger.level;
// Mocha doesn't monitor unhandled Promise rejections, so convert them to uncaught exceptions.
// https://github.com/mochajs/mocha/issues/2640
process.on('unhandledRejection', (reason, promise) => { throw reason; });
process.on('unhandledRejection', (reason: string) => { throw reason; });
before(async function () {
this.timeout(60000);
@ -67,7 +69,7 @@ exports.init = async function () {
await server.exit();
});
agentResolve(exports.agent);
agentResolve!(exports.agent);
return exports.agent;
};
@ -79,7 +81,7 @@ exports.init = async function () {
* @param {string} event - The socket.io Socket event to listen for.
* @returns The argument(s) passed to the event handler.
*/
exports.waitForSocketEvent = async (socket, event) => {
exports.waitForSocketEvent = async (socket: any, event:string) => {
const errorEvents = [
'error',
'connect_error',
@ -90,7 +92,7 @@ exports.waitForSocketEvent = async (socket, event) => {
const handlers = new Map();
let cancelTimeout;
try {
const timeoutP = new Promise((resolve, reject) => {
const timeoutP = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`timed out waiting for ${event} event`));
cancelTimeout = () => {};
@ -102,14 +104,14 @@ exports.waitForSocketEvent = async (socket, event) => {
};
});
const errorEventP = Promise.race(errorEvents.map((event) => new Promise((resolve, reject) => {
handlers.set(event, (errorString) => {
handlers.set(event, (errorString:string) => {
logger.debug(`socket.io ${event} event: ${errorString}`);
reject(new Error(errorString));
});
})));
const eventP = new Promise((resolve) => {
const eventP = new Promise<string|string[]>((resolve) => {
// This will overwrite one of the above handlers if the user is waiting for an error event.
handlers.set(event, (...args) => {
handlers.set(event, (...args:string[]) => {
logger.debug(`socket.io ${event} event`);
if (args.length > 1) return resolve(args);
resolve(args[0]);
@ -121,7 +123,7 @@ exports.waitForSocketEvent = async (socket, event) => {
// the event arrives).
return await Promise.race([timeoutP, errorEventP, eventP]);
} finally {
cancelTimeout();
cancelTimeout!();
for (const [event, handler] of handlers) socket.off(event, handler);
}
};
@ -134,10 +136,11 @@ exports.waitForSocketEvent = async (socket, event) => {
* nullish, no cookies are passed to the server.
* @returns {io.Socket} A socket.io client Socket object.
*/
exports.connect = async (res = null) => {
exports.connect = async (res:any = null) => {
// Convert the `set-cookie` header(s) into a `cookie` header.
const resCookies = (res == null) ? {} : setCookieParser.parse(res, {map: true});
const reqCookieHdr = Object.entries(resCookies).map(
// @ts-ignore
([name, cookie]) => `${name}=${encodeURIComponent(cookie.value)}`).join('; ');
logger.debug('socket.io connecting...');
@ -167,9 +170,10 @@ exports.connect = async (res = null) => {
*
* @param {io.Socket} socket - Connected socket.io Socket object.
* @param {string} padId - Which pad to join.
* @param token
* @returns The CLIENT_VARS message from the server.
*/
exports.handshake = async (socket, padId, token = padutils.generateAuthorToken()) => {
exports.handshake = async (socket: any, padId:string, token = padutils.generateAuthorToken()) => {
logger.debug('sending CLIENT_READY...');
socket.emit('message', {
component: 'pad',
@ -187,8 +191,11 @@ exports.handshake = async (socket, padId, token = padutils.generateAuthorToken()
/**
* Convenience wrapper around `socket.send()` that waits for acknowledgement.
*/
exports.sendMessage = async (socket, message) => await new Promise((resolve, reject) => {
socket.emit('message', message, (errInfo) => {
exports.sendMessage = async (socket: any, message:any) => await new Promise<void>((resolve, reject) => {
socket.emit('message', message, (errInfo:{
name: string,
message: string,
}) => {
if (errInfo != null) {
const {name, message} = errInfo;
const err = new Error(message);
@ -203,7 +210,7 @@ exports.sendMessage = async (socket, message) => await new Promise((resolve, rej
/**
* Convenience function to send a USER_CHANGES message. Waits for acknowledgement.
*/
exports.sendUserChanges = async (socket, data) => await exports.sendMessage(socket, {
exports.sendUserChanges = async (socket:any, data:any) => await exports.sendMessage(socket, {
type: 'COLLABROOM',
component: 'pad',
data: {
@ -225,7 +232,7 @@ exports.sendUserChanges = async (socket, data) => await exports.sendMessage(sock
* common.sendUserChanges(socket, {baseRev: rev, changeset}),
* ]);
*/
exports.waitForAcceptCommit = async (socket, wantRev) => {
exports.waitForAcceptCommit = async (socket:any, wantRev: number) => {
const msg = await exports.waitForSocketEvent(socket, 'message');
assert.deepEqual(msg, {
type: 'COLLABROOM',
@ -245,7 +252,7 @@ const alphabet = 'abcdefghijklmnopqrstuvwxyz';
* @param {string} [charset] - Characters to pick from.
* @returns {string}
*/
exports.randomString = (len = 10, charset = `${alphabet}${alphabet.toUpperCase()}0123456789`) => {
exports.randomString = (len: number = 10, charset: string = `${alphabet}${alphabet.toUpperCase()}0123456789`): string => {
let ret = '';
while (ret.length < len) ret += charset[Math.floor(Math.random() * charset.length)];
return ret;

View file

@ -2,16 +2,16 @@
* Fuzz testing the import endpoint
* Usage: node fuzzImportTest.js
*/
const settings = require('../container/loadSettings').loadSettings();
const common = require('./common');
const host = `http://${settings.ip}:${settings.port}`;
const froth = require('mocha-froth');
const settings = require('../container/loadSettings').loadSettings();
const axios = require('axios');
const apiKey = common.apiKey;
const apiVersion = 1;
const testPadId = `TEST_fuzz${makeid()}`;
const endPoint = function (point, version) {
const endPoint = function (point: string, version?:number) {
version = version || apiVersion;
return `/api/${version}/${point}?apikey=${apiKey}`;
};
@ -28,7 +28,7 @@ setTimeout(() => {
}
}, 5000); // wait 5 seconds
async function runTest(number) {
async function runTest(number: number) {
await axios.get(`${host + endPoint('createPad')}&padID=${testPadId}`)
.then(() => {
const req = axios.post(`${host}/p/${testPadId}/import`)
@ -51,8 +51,9 @@ async function runTest(number) {
});
});
})
.catch(err => {
throw new Error('FAILURE', err);
.catch((err:any) => {
// @ts-ignore
throw new Error('FAILURE', err);
})
}

View file

@ -8,7 +8,7 @@ const plugins = require('../../../static/js/pluginfw/plugin_defs');
const readOnlyManager = require('../../../node/db/ReadOnlyManager');
describe(__filename, function () {
let padId;
let padId:string;
beforeEach(async function () {
padId = common.randomString();
@ -16,7 +16,7 @@ describe(__filename, function () {
});
describe('exportEtherpadAdditionalContent', function () {
let hookBackup;
let hookBackup: ()=>void;
before(async function () {
hookBackup = plugins.hooks.exportEtherpadAdditionalContent || [];

View file

@ -1,5 +1,7 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
const assert = require('assert').strict;
const authorManager = require('../../../node/db/AuthorManager');
const db = require('../../../node/db/DB');
@ -9,11 +11,11 @@ const plugins = require('../../../static/js/pluginfw/plugin_defs');
const {randomString} = require('../../../static/js/pad_utils');
describe(__filename, function () {
let padId;
let padId: string;
const makeAuthorId = () => `a.${randomString(16)}`;
const makeExport = (authorId) => ({
const makeExport = (authorId: string) => ({
'pad:testing': {
atext: {
text: 'foo\n',
@ -65,7 +67,7 @@ describe(__filename, function () {
it('changes are all or nothing', async function () {
const authorId = makeAuthorId();
const data = makeExport(authorId);
const data:MapArrayType<any> = makeExport(authorId);
data['pad:differentPadId:revs:0'] = data['pad:testing:revs:0'];
delete data['pad:testing:revs:0'];
assert.rejects(importEtherpad.setPadRaw(padId, JSON.stringify(data)), /unexpected pad ID/);
@ -74,8 +76,8 @@ describe(__filename, function () {
});
describe('author pad IDs', function () {
let existingAuthorId;
let newAuthorId;
let existingAuthorId: string;
let newAuthorId:string;
beforeEach(async function () {
existingAuthorId = (await authorManager.createAuthor('existing')).authorID;
@ -133,7 +135,7 @@ describe(__filename, function () {
describe('enforces consistent pad ID', function () {
it('pad record has different pad ID', async function () {
const data = makeExport(makeAuthorId());
const data:MapArrayType<any> = makeExport(makeAuthorId());
data['pad:differentPadId'] = data['pad:testing'];
delete data['pad:testing'];
assert.rejects(importEtherpad.setPadRaw(padId, JSON.stringify(data)), /unexpected pad ID/);
@ -147,7 +149,7 @@ describe(__filename, function () {
});
it('pad rev record has different pad ID', async function () {
const data = makeExport(makeAuthorId());
const data:MapArrayType<any> = makeExport(makeAuthorId());
data['pad:differentPadId:revs:0'] = data['pad:testing:revs:0'];
delete data['pad:testing:revs:0'];
assert.rejects(importEtherpad.setPadRaw(padId, JSON.stringify(data)), /unexpected pad ID/);
@ -170,7 +172,7 @@ describe(__filename, function () {
});
describe('exportEtherpadAdditionalContent', function () {
let hookBackup;
let hookBackup: Function;
before(async function () {
hookBackup = plugins.hooks.exportEtherpadAdditionalContent || [];

View file

@ -1,7 +1,10 @@
'use strict';
import {PadType} from "../../../node/types/PadType";
const Pad = require('../../../node/db/Pad');
const assert = require('assert').strict;
import { strict as assert } from 'assert';
import {MapArrayType} from "../../../node/types/MapType";
const authorManager = require('../../../node/db/AuthorManager');
const common = require('../common');
const padManager = require('../../../node/db/PadManager');
@ -9,9 +12,9 @@ const plugins = require('../../../static/js/pluginfw/plugin_defs');
const settings = require('../../../node/utils/Settings');
describe(__filename, function () {
const backups = {};
let pad;
let padId;
const backups:MapArrayType<any> = {};
let pad: PadType|null;
let padId: string;
before(async function () {
backups.hooks = {
@ -52,7 +55,7 @@ describe(__filename, function () {
describe('padDefaultContent hook', function () {
it('runs when a pad is created without specific text', async function () {
const p = new Promise((resolve) => {
const p = new Promise<void>((resolve) => {
plugins.hooks.padDefaultContent.push({hook_fn: () => resolve()});
});
pad = await padManager.getPad(padId);
@ -66,8 +69,8 @@ describe(__filename, function () {
});
it('defaults to settings.defaultPadText', async function () {
const p = new Promise((resolve, reject) => {
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName, ctx) => {
const p = new Promise<void>((resolve, reject) => {
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName:string, ctx:any) => {
try {
assert.equal(ctx.type, 'text');
assert.equal(ctx.content, settings.defaultPadText);
@ -83,7 +86,9 @@ describe(__filename, function () {
it('passes the pad object', async function () {
const gotP = new Promise((resolve) => {
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName, {pad}) => resolve(pad)});
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName:string, {pad}:{
pad: PadType,
}) => resolve(pad)});
});
pad = await padManager.getPad(padId);
assert.equal(await gotP, pad);
@ -92,7 +97,9 @@ describe(__filename, function () {
it('passes empty authorId if not provided', async function () {
const gotP = new Promise((resolve) => {
plugins.hooks.padDefaultContent.push(
{hook_fn: async (hookName, {authorId}) => resolve(authorId)});
{hook_fn: async (hookName:string, {authorId}:{
authorId: string,
}) => resolve(authorId)});
});
pad = await padManager.getPad(padId);
assert.equal(await gotP, '');
@ -102,7 +109,9 @@ describe(__filename, function () {
const want = await authorManager.getAuthor4Token(`t.${padId}`);
const gotP = new Promise((resolve) => {
plugins.hooks.padDefaultContent.push(
{hook_fn: async (hookName, {authorId}) => resolve(authorId)});
{hook_fn: async (hookName: string, {authorId}:{
authorId: string,
}) => resolve(authorId)});
});
pad = await padManager.getPad(padId, null, want);
assert.equal(await gotP, want);
@ -111,24 +120,24 @@ describe(__filename, function () {
it('uses provided content', async function () {
const want = 'hello world';
assert.notEqual(want, settings.defaultPadText);
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName, ctx) => {
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName:string, ctx:any) => {
ctx.type = 'text';
ctx.content = want;
}});
pad = await padManager.getPad(padId);
assert.equal(pad.text(), `${want}\n`);
assert.equal(pad!.text(), `${want}\n`);
});
it('cleans provided content', async function () {
const input = 'foo\r\nbar\r\tbaz';
const want = 'foo\nbar\n baz';
assert.notEqual(want, settings.defaultPadText);
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName, ctx) => {
plugins.hooks.padDefaultContent.push({hook_fn: async (hookName:string, ctx:any) => {
ctx.type = 'text';
ctx.content = input;
}});
pad = await padManager.getPad(padId);
assert.equal(pad.text(), `${want}\n`);
assert.equal(pad!.text(), `${want}\n`);
});
});
});

View file

@ -1,6 +1,6 @@
'use strict';
const assert = require('assert').strict;
import {strict} from "assert";
const common = require('../common');
const crypto = require('../../../node/security/crypto');
const db = require('../../../node/db/DB');
@ -9,17 +9,23 @@ const SecretRotator = require("../../../node/security/SecretRotator").SecretRota
const logger = common.logger;
// Greatest common divisor.
const gcd = (...args) => (
const gcd: Function = (...args:number[]) => (
args.length === 1 ? args[0]
: args.length === 2 ? ((args[1]) ? gcd(args[1], args[0] % args[1]) : Math.abs(args[0]))
: gcd(args[0], gcd(...args.slice(1))));
// Least common multiple.
const lcm = (...args) => (
const lcm:Function = (...args: number[]) => (
args.length === 1 ? args[0]
: args.length === 2 ? Math.abs(args[0] * args[1]) / gcd(...args)
: lcm(args[0], lcm(...args.slice(1))));
class FakeClock {
_now: number;
_nextId: number;
_idle: Promise<any>;
timeouts: Map<number, any>;
constructor() {
logger.debug('new fake clock');
this._now = 0;
@ -29,10 +35,10 @@ class FakeClock {
}
_next() { return Math.min(...[...this.timeouts.values()].map((x) => x.when)); }
async setNow(t) {
async setNow(t: number) {
logger.debug(`setting fake time to ${t}`);
assert(t >= this._now);
assert(t < Infinity);
strict(t >= this._now);
strict(t < Infinity);
let n;
while ((n = this._next()) <= t) {
this._now = Math.max(this._now, Math.min(n, t));
@ -42,7 +48,7 @@ class FakeClock {
this._now = t;
logger.debug(`fake time set to ${this._now}`);
}
async advance(t) { await this.setNow(this._now + t); }
async advance(t: number) { await this.setNow(this._now + t); }
async advanceToNext() {
const n = this._next();
if (n < this._now) await this._fire();
@ -68,34 +74,34 @@ class FakeClock {
}
get now() { return this._now; }
setTimeout(fn, wait = 0) {
setTimeout(fn:Function, wait = 0) {
const when = this._now + wait;
const id = this._nextId++;
this.timeouts.set(id, {id, fn, when});
this._fire();
return id;
}
clearTimeout(id) { this.timeouts.delete(id); }
clearTimeout(id:number) { this.timeouts.delete(id); }
}
// In JavaScript, the % operator is remainder, not modulus.
const mod = (a, n) => ((a % n) + n) % n;
const mod = (a: number, n:number) => ((a % n) + n) % n;
describe(__filename, function () {
let dbPrefix;
let sr;
let dbPrefix: string;
let sr: any;
let interval = 1e3;
const lifetime = 1e4;
const intervalStart = (t) => t - mod(t, interval);
const hkdf = async (secret, salt, tN) => Buffer.from(
const intervalStart = (t: number) => t - mod(t, interval);
const hkdf = async (secret: string, salt:string, tN:number) => Buffer.from(
await crypto.hkdf('sha256', secret, salt, `${tN}`, 32)).toString('hex');
const newRotator = (s = null) => new SecretRotator(dbPrefix, interval, lifetime, s);
const newRotator = (s:string|null = null) => new SecretRotator(dbPrefix, interval, lifetime, s);
const setFakeClock = (sr, fc = null) => {
const setFakeClock = (sr: { _t: { now: () => number; setTimeout: (fn: Function, wait?: number) => number; clearTimeout: (id: number) => void; }; }, fc:FakeClock|null = null) => {
if (fc == null) fc = new FakeClock();
sr._t = {
now: () => fc.now,
now: () => fc!.now,
setTimeout: fc.setTimeout.bind(fc),
clearTimeout: fc.clearTimeout.bind(fc),
};
@ -115,19 +121,19 @@ describe(__filename, function () {
if (sr != null) sr.stop();
sr = null;
await Promise.all(
(await db.findKeys(`${dbPrefix}:*`, null)).map(async (dbKey) => await db.remove(dbKey)));
(await db.findKeys(`${dbPrefix}:*`, null)).map(async (dbKey: string) => await db.remove(dbKey)));
});
describe('constructor', function () {
it('creates empty secrets array', async function () {
sr = newRotator();
assert.deepEqual(sr.secrets, []);
strict.deepEqual(sr.secrets, []);
});
for (const invalidChar of '*:%') {
it(`rejects database prefixes containing ${invalidChar}`, async function () {
dbPrefix += invalidChar;
assert.throws(newRotator, /invalid char/);
strict.throws(newRotator, /invalid char/);
});
}
});
@ -138,19 +144,19 @@ describe(__filename, function () {
setFakeClock(sr);
const {secrets} = sr;
await sr.start();
assert.equal(sr.secrets, secrets);
strict.equal(sr.secrets, secrets);
});
it('derives secrets', async function () {
sr = newRotator();
setFakeClock(sr);
await sr.start();
assert.equal(sr.secrets.length, 3); // Current (active), previous, and next.
strict.equal(sr.secrets.length, 3); // Current (active), previous, and next.
for (const s of sr.secrets) {
assert.equal(typeof s, 'string');
assert(s);
strict.equal(typeof s, 'string');
strict(s);
}
assert.equal(new Set(sr.secrets).size, sr.secrets.length); // The secrets should all differ.
strict.equal(new Set(sr.secrets).size, sr.secrets.length); // The secrets should all differ.
});
it('publishes params', async function () {
@ -158,13 +164,13 @@ describe(__filename, function () {
const fc = setFakeClock(sr);
await sr.start();
const dbKeys = await db.findKeys(`${dbPrefix}:*`, null);
assert.equal(dbKeys.length, 1);
strict.equal(dbKeys.length, 1);
const [id] = dbKeys;
assert(id.startsWith(`${dbPrefix}:`));
assert.notEqual(id.slice(dbPrefix.length + 1), '');
strict(id.startsWith(`${dbPrefix}:`));
strict.notEqual(id.slice(dbPrefix.length + 1), '');
const p = await db.get(id);
const {secret, salt} = p.algParams;
assert.deepEqual(p, {
strict.deepEqual(p, {
algId: 1,
algParams: {
digest: 'sha256',
@ -177,11 +183,11 @@ describe(__filename, function () {
interval,
lifetime,
});
assert.equal(typeof salt, 'string');
assert.match(salt, /^[0-9a-f]{64}$/);
assert.equal(typeof secret, 'string');
assert.match(secret, /^[0-9a-f]{64}$/);
assert.deepEqual(sr.secrets, await Promise.all(
strict.equal(typeof salt, 'string');
strict.match(salt, /^[0-9a-f]{64}$/);
strict.equal(typeof secret, 'string');
strict.match(secret, /^[0-9a-f]{64}$/);
strict.deepEqual(sr.secrets, await Promise.all(
[0, -interval, interval].map(async (tN) => await hkdf(secret, salt, tN))));
});
@ -195,8 +201,8 @@ describe(__filename, function () {
sr = newRotator();
setFakeClock(sr, fc);
await sr.start();
assert.deepEqual(sr.secrets, secrets);
assert.deepEqual(await db.findKeys(`${dbPrefix}:*`, null), dbKeys);
strict.deepEqual(sr.secrets, secrets);
strict.deepEqual(await db.findKeys(`${dbPrefix}:*`, null), dbKeys);
});
it('deletes expired publications', async function () {
@ -204,7 +210,7 @@ describe(__filename, function () {
const fc = setFakeClock(sr);
await sr.start();
const [oldId] = await db.findKeys(`${dbPrefix}:*`, null);
assert(oldId != null);
strict(oldId != null);
sr.stop();
const p = await db.get(oldId);
await fc.setNow(p.end + p.lifetime + p.interval);
@ -212,9 +218,9 @@ describe(__filename, function () {
setFakeClock(sr, fc);
await sr.start();
const ids = await db.findKeys(`${dbPrefix}:*`, null);
assert.equal(ids.length, 1);
strict.equal(ids.length, 1);
const [newId] = ids;
assert.notEqual(newId, oldId);
strict.notEqual(newId, oldId);
});
it('keeps expired publications until interval past expiration', async function () {
@ -229,23 +235,23 @@ describe(__filename, function () {
sr = newRotator();
setFakeClock(sr, fc);
await sr.start();
assert(sr.secrets.slice(1).includes(future));
strict(sr.secrets.slice(1).includes(future));
// It should have created a new publication, not extended the life of the old publication.
assert.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 2);
assert.deepEqual(await db.get(origId), p);
strict.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 2);
strict.deepEqual(await db.get(origId), p);
});
it('idempotent', async function () {
sr = newRotator();
const fc = setFakeClock(sr);
await sr.start();
assert.equal(fc.timeouts.size, 1);
strict.equal(fc.timeouts.size, 1);
const secrets = [...sr.secrets];
const dbKeys = await db.findKeys(`${dbPrefix}:*`, null);
await sr.start();
assert.equal(fc.timeouts.size, 1);
assert.deepEqual(sr.secrets, secrets);
assert.deepEqual(await db.findKeys(`${dbPrefix}:*`, null), dbKeys);
strict.equal(fc.timeouts.size, 1);
strict.deepEqual(sr.secrets, secrets);
strict.deepEqual(await db.findKeys(`${dbPrefix}:*`, null), dbKeys);
});
describe(`schedules update at next interval (= ${interval})`, function () {
@ -262,16 +268,16 @@ describe(__filename, function () {
const fc = setFakeClock(sr);
await fc.setNow(now);
await sr.start();
assert.equal(fc.timeouts.size, 1);
strict.equal(fc.timeouts.size, 1);
const [{when}] = fc.timeouts.values();
assert.equal(when, want);
strict.equal(when, want);
});
}
it('multiple active params with different intervals', async function () {
const intervals = [400, 600, 1000];
const lcmi = lcm(...intervals);
const wants = new Set();
const wants:Set<number> = new Set();
for (const i of intervals) for (let t = i; t <= lcmi; t += i) wants.add(t);
const fcs = new FakeClock();
const srs = intervals.map((i) => {
@ -290,7 +296,7 @@ describe(__filename, function () {
logger.debug(`next timeout should be at ${want}`);
await fc.advanceToNext();
await fcs.setNow(fc.now); // Keep all of the publications alive.
assert.equal(fc.now, want);
strict.equal(fc.now, want);
}
} finally {
for (const sr of srs) sr.stop();
@ -304,9 +310,9 @@ describe(__filename, function () {
sr = newRotator();
const fc = setFakeClock(sr);
await sr.start();
assert.notEqual(fc.timeouts.size, 0);
strict.notEqual(fc.timeouts.size, 0);
sr.stop();
assert.equal(fc.timeouts.size, 0);
strict.equal(fc.timeouts.size, 0);
});
it('safe to call multiple times', async function () {
@ -325,14 +331,14 @@ describe(__filename, function () {
// Use a time that isn't a multiple of interval in case there is a modular arithmetic bug that
// would otherwise go undetected.
await fc.setNow(1);
assert(mod(fc.now, interval) !== 0);
strict(mod(fc.now, interval) !== 0);
await sr.start();
assert.equal(sr.secrets.length, 4); // 1 for the legacy secret, 3 for past, current, future
assert(sr.secrets.slice(1).includes('legacy')); // Should not be the current secret.
strict.equal(sr.secrets.length, 4); // 1 for the legacy secret, 3 for past, current, future
strict(sr.secrets.slice(1).includes('legacy')); // Should not be the current secret.
const ids = await db.findKeys(`${dbPrefix}:*`, null);
const params = (await Promise.all(ids.map(async (id) => await db.get(id))))
const params = (await Promise.all(ids.map(async (id:string) => await db.get(id))))
.sort((a, b) => a.algId - b.algId);
assert.deepEqual(params, [
strict.deepEqual(params, [
{
algId: 0,
algParams: 'legacy',
@ -358,26 +364,26 @@ describe(__filename, function () {
sr = newRotator();
const fc = setFakeClock(sr);
await fc.setNow(1);
assert(mod(fc.now, interval) !== 0);
strict(mod(fc.now, interval) !== 0);
const wantTime = fc.now;
await sr.start();
assert.equal(sr.secrets.length, 3);
strict.equal(sr.secrets.length, 3);
const [s1, s0, s2] = sr.secrets; // s1=current, s0=previous, s2=next
sr.stop();
// Use a time that is not a multiple of interval off of epoch or wantTime just in case there
// is a modular arithmetic bug that would otherwise go undetected.
await fc.advance(interval + 1);
assert(mod(fc.now, interval) !== 0);
assert(mod(fc.now - wantTime, interval) !== 0);
strict(mod(fc.now, interval) !== 0);
strict(mod(fc.now - wantTime, interval) !== 0);
sr = newRotator('legacy');
setFakeClock(sr, fc);
await sr.start();
assert.equal(sr.secrets.length, 5); // s0 through s3 and the legacy secret.
assert.deepEqual(sr.secrets, [s2, s1, s0, sr.secrets[3], 'legacy']);
strict.equal(sr.secrets.length, 5); // s0 through s3 and the legacy secret.
strict.deepEqual(sr.secrets, [s2, s1, s0, sr.secrets[3], 'legacy']);
const ids = await db.findKeys(`${dbPrefix}:*`, null);
const params = (await Promise.all(ids.map(async (id) => await db.get(id))))
const params = (await Promise.all(ids.map(async (id:string) => await db.get(id))))
.sort((a, b) => a.algId - b.algId);
assert.deepEqual(params, [
strict.deepEqual(params, [
{
algId: 0,
algParams: 'legacy',
@ -405,8 +411,8 @@ describe(__filename, function () {
sr = newRotator('legacy2');
setFakeClock(sr, fc);
await sr.start();
assert(sr.secrets.slice(1).includes('legacy1'));
assert(sr.secrets.slice(1).includes('legacy2'));
strict(sr.secrets.slice(1).includes('legacy1'));
strict(sr.secrets.slice(1).includes('legacy2'));
});
it('multiple instances with the same legacy secret', async function () {
@ -417,9 +423,9 @@ describe(__filename, function () {
sr = newRotator('legacy');
setFakeClock(sr, fc);
await sr.start();
assert.deepEqual(sr.secrets, [...new Set(sr.secrets)]);
strict.deepEqual(sr.secrets, [...new Set(sr.secrets)]);
// There shouldn't be multiple publications for the same legacy secret.
assert.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 2);
strict.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 2);
});
it('legacy secret is included for interval after expiration', async function () {
@ -431,7 +437,7 @@ describe(__filename, function () {
sr = newRotator('legacy');
setFakeClock(sr, fc);
await sr.start();
assert(sr.secrets.slice(1).includes('legacy'));
strict(sr.secrets.slice(1).includes('legacy'));
});
it('legacy secret is not included if the oldest secret is old enough', async function () {
@ -443,7 +449,7 @@ describe(__filename, function () {
sr = newRotator('legacy');
setFakeClock(sr, fc);
await sr.start();
assert(!sr.secrets.includes('legacy'));
strict(!sr.secrets.includes('legacy'));
});
it('dead secrets still affect legacy secret end time', async function () {
@ -456,8 +462,8 @@ describe(__filename, function () {
sr = newRotator('legacy');
setFakeClock(sr, fc);
await sr.start();
assert(!sr.secrets.includes('legacy'));
assert(!sr.secrets.some((s) => secrets.has(s)));
strict(!sr.secrets.includes('legacy'));
strict(!sr.secrets.some((s:string) => secrets.has(s)));
});
});
@ -465,11 +471,11 @@ describe(__filename, function () {
it('no rotation before start of interval', async function () {
sr = newRotator();
const fc = setFakeClock(sr);
assert.equal(fc.now, 0);
strict.equal(fc.now, 0);
await sr.start();
const secrets = [...sr.secrets];
await fc.advance(interval - 1);
assert.deepEqual(sr.secrets, secrets);
strict.deepEqual(sr.secrets, secrets);
});
it('does not replace secrets array', async function () {
@ -479,8 +485,8 @@ describe(__filename, function () {
const [current] = sr.secrets;
const secrets = sr.secrets;
await fc.advance(interval);
assert.notEqual(sr.secrets[0], current);
assert.equal(sr.secrets, secrets);
strict.notEqual(sr.secrets[0], current);
strict.equal(sr.secrets, secrets);
});
it('future secret becomes current, new future is generated', async function () {
@ -488,11 +494,11 @@ describe(__filename, function () {
const fc = setFakeClock(sr);
await sr.start();
const secrets = new Set(sr.secrets);
assert.equal(secrets.size, 3);
strict.equal(secrets.size, 3);
const [s1, s0, s2] = sr.secrets;
await fc.advance(interval);
assert.deepEqual(sr.secrets, [s2, s1, s0, sr.secrets[3]]);
assert(!secrets.has(sr.secrets[3]));
strict.deepEqual(sr.secrets, [s2, s1, s0, sr.secrets[3]]);
strict(!secrets.has(sr.secrets[3]));
});
it('expired publications are deleted', async function () {
@ -505,9 +511,9 @@ describe(__filename, function () {
sr = newRotator();
setFakeClock(sr, fc);
await sr.start();
assert.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 2);
strict.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 2);
await fc.advance(lifetime + (3 * origInterval));
assert.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 1);
strict.equal((await db.findKeys(`${dbPrefix}:*`, null)).length, 1);
});
it('old secrets are eventually removed', async function () {
@ -516,9 +522,9 @@ describe(__filename, function () {
await sr.start();
const [, s0] = sr.secrets;
await fc.advance(lifetime + interval - 1);
assert(sr.secrets.slice(1).includes(s0));
strict(sr.secrets.slice(1).includes(s0));
await fc.advance(1);
assert(!sr.secrets.includes(s0));
strict(!sr.secrets.includes(s0));
});
});
@ -527,19 +533,19 @@ describe(__filename, function () {
const srs = [newRotator(), newRotator()];
const fcs = srs.map((sr) => setFakeClock(sr));
for (const sr of srs) await sr.start(); // Don't use Promise.all() otherwise they race.
assert.deepEqual(srs[0].secrets, srs[1].secrets);
strict.deepEqual(srs[0].secrets, srs[1].secrets);
// Advance fcs[0] to the end of the interval after fcs[1].
await fcs[0].advance((2 * interval) - 1);
assert(srs[0].secrets.includes(srs[1].secrets[0]));
assert(srs[1].secrets.includes(srs[0].secrets[0]));
strict(srs[0].secrets.includes(srs[1].secrets[0]));
strict(srs[1].secrets.includes(srs[0].secrets[0]));
// Advance both by an interval.
await Promise.all([fcs[1].advance(interval), fcs[0].advance(interval)]);
assert(srs[0].secrets.includes(srs[1].secrets[0]));
assert(srs[1].secrets.includes(srs[0].secrets[0]));
strict(srs[0].secrets.includes(srs[1].secrets[0]));
strict(srs[1].secrets.includes(srs[0].secrets[0]));
// Advance fcs[1] to the end of the interval after fcs[0].
await Promise.all([fcs[1].advance((3 * interval) - 1), fcs[0].advance(1)]);
assert(srs[0].secrets.includes(srs[1].secrets[0]));
assert(srs[1].secrets.includes(srs[0].secrets[0]));
strict(srs[0].secrets.includes(srs[1].secrets[0]));
strict(srs[1].secrets.includes(srs[0].secrets[0]));
});
it('start up out of sync', async function () {
@ -548,8 +554,8 @@ describe(__filename, function () {
await fcs[0].advance((2 * interval) - 1);
await srs[0].start(); // Must start before srs[1] so that srs[1] starts in srs[0]'s past.
await srs[1].start();
assert(srs[0].secrets.includes(srs[1].secrets[0]));
assert(srs[1].secrets.includes(srs[0].secrets[0]));
strict(srs[0].secrets.includes(srs[1].secrets[0]));
strict(srs[1].secrets.includes(srs[0].secrets[0]));
});
});
});

View file

@ -1,19 +1,27 @@
'use strict';
const SessionStore = require('../../../node/db/SessionStore');
const assert = require('assert').strict;
import {strict as assert} from 'assert';
const common = require('../common');
const db = require('../../../node/db/DB');
const util = require('util');
import util from 'util';
type Session = {
set: (sid: string|null,sess:any, sess2:any) => void;
get: (sid:string|null) => any;
destroy: (sid:string|null) => void;
touch: (sid:string|null, sess:any, sess2:any) => void;
shutdown: () => void;
}
describe(__filename, function () {
let ss;
let sid;
let ss: Session|null;
let sid: string|null;
const set = async (sess) => await util.promisify(ss.set).call(ss, sid, sess);
const get = async () => await util.promisify(ss.get).call(ss, sid);
const destroy = async () => await util.promisify(ss.destroy).call(ss, sid);
const touch = async (sess) => await util.promisify(ss.touch).call(ss, sid, sess);
const set = async (sess: string|null) => await util.promisify(ss!.set).call(ss, sid, sess);
const get = async () => await util.promisify(ss!.get).call(ss, sid);
const destroy = async () => await util.promisify(ss!.destroy).call(ss, sid);
const touch = async (sess: Session) => await util.promisify(ss!.touch).call(ss, sid, sess);
before(async function () {
await common.init();
@ -40,13 +48,13 @@ describe(__filename, function () {
});
it('set of non-expiring session', async function () {
const sess = {foo: 'bar', baz: {asdf: 'jkl;'}};
const sess:any = {foo: 'bar', baz: {asdf: 'jkl;'}};
await set(sess);
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
});
it('set of session that expires', async function () {
const sess = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
await set(sess);
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
await new Promise((resolve) => setTimeout(resolve, 110));
@ -55,25 +63,25 @@ describe(__filename, function () {
});
it('set of already expired session', async function () {
const sess = {foo: 'bar', cookie: {expires: new Date(1)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(1)}};
await set(sess);
// No record should have been created.
assert(await db.get(`sessionstorage:${sid}`) == null);
});
it('switch from non-expiring to expiring', async function () {
const sess = {foo: 'bar'};
const sess:any = {foo: 'bar'};
await set(sess);
const sess2 = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess2:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
await set(sess2);
await new Promise((resolve) => setTimeout(resolve, 110));
assert(await db.get(`sessionstorage:${sid}`) == null);
});
it('switch from expiring to non-expiring', async function () {
const sess = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
await set(sess);
const sess2 = {foo: 'bar'};
const sess2:any = {foo: 'bar'};
await set(sess2);
await new Promise((resolve) => setTimeout(resolve, 110));
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess2));
@ -86,7 +94,7 @@ describe(__filename, function () {
});
it('set+get round trip', async function () {
const sess = {foo: 'bar', baz: {asdf: 'jkl;'}};
const sess:any = {foo: 'bar', baz: {asdf: 'jkl;'}};
await set(sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
});
@ -114,7 +122,7 @@ describe(__filename, function () {
});
it('external expiration update is picked up', async function () {
const sess = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
await set(sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
const sess2 = {...sess, cookie: {expires: new Date(Date.now() + 200)}};
@ -128,10 +136,10 @@ describe(__filename, function () {
describe('shutdown', function () {
it('shutdown cancels timeouts', async function () {
const sess = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
await set(sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
ss.shutdown();
ss!.shutdown();
await new Promise((resolve) => setTimeout(resolve, 110));
// The record should not have been automatically purged.
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
@ -140,14 +148,14 @@ describe(__filename, function () {
describe('destroy', function () {
it('destroy deletes the database record', async function () {
const sess = {cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {cookie: {expires: new Date(Date.now() + 100)}};
await set(sess);
await destroy();
assert(await db.get(`sessionstorage:${sid}`) == null);
});
it('destroy cancels the timeout', async function () {
const sess = {cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {cookie: {expires: new Date(Date.now() + 100)}};
await set(sess);
await destroy();
await db.set(`sessionstorage:${sid}`, sess);
@ -162,16 +170,16 @@ describe(__filename, function () {
describe('touch without refresh', function () {
it('touch before set is equivalent to set if session expires', async function () {
const sess = {cookie: {expires: new Date(Date.now() + 1000)}};
const sess:any = {cookie: {expires: new Date(Date.now() + 1000)}};
await touch(sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
});
it('touch updates observed expiration but not database', async function () {
const start = Date.now();
const sess = {cookie: {expires: new Date(start + 200)}};
const sess:any = {cookie: {expires: new Date(start + 200)}};
await set(sess);
const sess2 = {cookie: {expires: new Date(start + 12000)}};
const sess2:any = {cookie: {expires: new Date(start + 12000)}};
await touch(sess2);
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
assert.equal(JSON.stringify(await get()), JSON.stringify(sess2));
@ -184,16 +192,16 @@ describe(__filename, function () {
});
it('touch before set is equivalent to set if session expires', async function () {
const sess = {cookie: {expires: new Date(Date.now() + 1000)}};
const sess:any = {cookie: {expires: new Date(Date.now() + 1000)}};
await touch(sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
});
it('touch before eligible for refresh updates expiration but not DB', async function () {
const now = Date.now();
const sess = {foo: 'bar', cookie: {expires: new Date(now + 1000)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(now + 1000)}};
await set(sess);
const sess2 = {foo: 'bar', cookie: {expires: new Date(now + 1001)}};
const sess2:any = {foo: 'bar', cookie: {expires: new Date(now + 1001)}};
await touch(sess2);
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
assert.equal(JSON.stringify(await get()), JSON.stringify(sess2));
@ -201,10 +209,10 @@ describe(__filename, function () {
it('touch before eligible for refresh updates timeout', async function () {
const start = Date.now();
const sess = {foo: 'bar', cookie: {expires: new Date(start + 200)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(start + 200)}};
await set(sess);
await new Promise((resolve) => setTimeout(resolve, 100));
const sess2 = {foo: 'bar', cookie: {expires: new Date(start + 399)}};
const sess2:any = {foo: 'bar', cookie: {expires: new Date(start + 399)}};
await touch(sess2);
await new Promise((resolve) => setTimeout(resolve, 110));
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
@ -213,10 +221,10 @@ describe(__filename, function () {
it('touch after eligible for refresh updates db', async function () {
const start = Date.now();
const sess = {foo: 'bar', cookie: {expires: new Date(start + 200)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(start + 200)}};
await set(sess);
await new Promise((resolve) => setTimeout(resolve, 100));
const sess2 = {foo: 'bar', cookie: {expires: new Date(start + 400)}};
const sess2:any = {foo: 'bar', cookie: {expires: new Date(start + 400)}};
await touch(sess2);
await new Promise((resolve) => setTimeout(resolve, 110));
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess2));
@ -225,7 +233,7 @@ describe(__filename, function () {
it('refresh=0 updates db every time', async function () {
ss = new SessionStore(0);
const sess = {foo: 'bar', cookie: {expires: new Date(Date.now() + 1000)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 1000)}};
await set(sess);
await db.remove(`sessionstorage:${sid}`);
await touch(sess); // No change in expiration time.

View file

@ -1,9 +1,12 @@
'use strict';
const Stream = require('../../../node/utils/Stream');
const assert = require('assert').strict;
import {strict} from "assert";
class DemoIterable {
private value: number;
errs: Error[];
rets: any[];
constructor() {
this.value = 0;
this.errs = [];
@ -17,14 +20,14 @@ class DemoIterable {
return {value: this.value++, done: false};
}
throw(err) {
throw(err: any) {
const alreadyCompleted = this.completed();
this.errs.push(err);
if (alreadyCompleted) throw err; // Mimic standard generator objects.
throw err;
}
return(ret) {
return(ret: number) {
const alreadyCompleted = this.completed();
this.rets.push(ret);
if (alreadyCompleted) return {value: ret, done: true}; // Mimic standard generator objects.
@ -34,65 +37,69 @@ class DemoIterable {
[Symbol.iterator]() { return this; }
}
const assertUnhandledRejection = async (action, want) => {
const assertUnhandledRejection = async (action: any, want: any) => {
// Temporarily remove unhandled Promise rejection listeners so that the unhandled rejections we
// expect to see don't trigger a test failure (or terminate node).
const event = 'unhandledRejection';
const listenersBackup = process.rawListeners(event);
process.removeAllListeners(event);
let tempListener;
let asyncErr;
let tempListener: Function;
let asyncErr:any;
try {
const seenErrPromise = new Promise((resolve) => {
tempListener = (err) => {
assert.equal(asyncErr, undefined);
const seenErrPromise = new Promise<void>((resolve) => {
tempListener = (err:any) => {
strict.equal(asyncErr, undefined);
asyncErr = err;
resolve();
};
});
// @ts-ignore
process.on(event, tempListener);
await action();
await seenErrPromise;
} finally {
// Restore the original listeners.
// @ts-ignore
process.off(event, tempListener);
for (const listener of listenersBackup) process.on(event, listener);
for (const listener of listenersBackup) { // @ts-ignore
process.on(event, listener);
}
}
await assert.rejects(Promise.reject(asyncErr), want);
await strict.rejects(Promise.reject(asyncErr), want);
};
describe(__filename, function () {
describe('basic behavior', function () {
it('takes a generator', async function () {
assert.deepEqual([...new Stream((function* () { yield 0; yield 1; yield 2; })())], [0, 1, 2]);
strict.deepEqual([...new Stream((function* () { yield 0; yield 1; yield 2; })())], [0, 1, 2]);
});
it('takes an array', async function () {
assert.deepEqual([...new Stream([0, 1, 2])], [0, 1, 2]);
strict.deepEqual([...new Stream([0, 1, 2])], [0, 1, 2]);
});
it('takes an iterator', async function () {
assert.deepEqual([...new Stream([0, 1, 2][Symbol.iterator]())], [0, 1, 2]);
strict.deepEqual([...new Stream([0, 1, 2][Symbol.iterator]())], [0, 1, 2]);
});
it('supports empty iterators', async function () {
assert.deepEqual([...new Stream([])], []);
strict.deepEqual([...new Stream([])], []);
});
it('is resumable', async function () {
const s = new Stream((function* () { yield 0; yield 1; yield 2; })());
let iter = s[Symbol.iterator]();
assert.deepEqual(iter.next(), {value: 0, done: false});
strict.deepEqual(iter.next(), {value: 0, done: false});
iter = s[Symbol.iterator]();
assert.deepEqual(iter.next(), {value: 1, done: false});
assert.deepEqual([...s], [2]);
strict.deepEqual(iter.next(), {value: 1, done: false});
strict.deepEqual([...s], [2]);
});
it('supports return value', async function () {
const s = new Stream((function* () { yield 0; return 1; })());
const iter = s[Symbol.iterator]();
assert.deepEqual(iter.next(), {value: 0, done: false});
assert.deepEqual(iter.next(), {value: 1, done: true});
strict.deepEqual(iter.next(), {value: 0, done: false});
strict.deepEqual(iter.next(), {value: 1, done: true});
});
it('does not start until needed', async function () {
@ -100,60 +107,60 @@ describe(__filename, function () {
new Stream((function* () { yield lastYield = 0; })());
// Fetching from the underlying iterator should not start until the first value is fetched
// from the stream.
assert.equal(lastYield, null);
strict.equal(lastYield, null);
});
it('throw is propagated', async function () {
const underlying = new DemoIterable();
const s = new Stream(underlying);
const iter = s[Symbol.iterator]();
assert.deepEqual(iter.next(), {value: 0, done: false});
strict.deepEqual(iter.next(), {value: 0, done: false});
const err = new Error('injected');
assert.throws(() => iter.throw(err), err);
assert.equal(underlying.errs[0], err);
strict.throws(() => iter.throw(err), err);
strict.equal(underlying.errs[0], err);
});
it('return is propagated', async function () {
const underlying = new DemoIterable();
const s = new Stream(underlying);
const iter = s[Symbol.iterator]();
assert.deepEqual(iter.next(), {value: 0, done: false});
assert.deepEqual(iter.return(42), {value: 42, done: true});
assert.equal(underlying.rets[0], 42);
strict.deepEqual(iter.next(), {value: 0, done: false});
strict.deepEqual(iter.return(42), {value: 42, done: true});
strict.equal(underlying.rets[0], 42);
});
});
describe('range', function () {
it('basic', async function () {
assert.deepEqual([...Stream.range(0, 3)], [0, 1, 2]);
strict.deepEqual([...Stream.range(0, 3)], [0, 1, 2]);
});
it('empty', async function () {
assert.deepEqual([...Stream.range(0, 0)], []);
strict.deepEqual([...Stream.range(0, 0)], []);
});
it('positive start', async function () {
assert.deepEqual([...Stream.range(3, 5)], [3, 4]);
strict.deepEqual([...Stream.range(3, 5)], [3, 4]);
});
it('negative start', async function () {
assert.deepEqual([...Stream.range(-3, 0)], [-3, -2, -1]);
strict.deepEqual([...Stream.range(-3, 0)], [-3, -2, -1]);
});
it('end before start', async function () {
assert.deepEqual([...Stream.range(3, 0)], []);
strict.deepEqual([...Stream.range(3, 0)], []);
});
});
describe('batch', function () {
it('empty', async function () {
assert.deepEqual([...new Stream([]).batch(10)], []);
strict.deepEqual([...new Stream([]).batch(10)], []);
});
it('does not start until needed', async function () {
let lastYield = null;
new Stream((function* () { yield lastYield = 0; })()).batch(10);
assert.equal(lastYield, null);
strict.equal(lastYield, null);
});
it('fewer than batch size', async function () {
@ -162,11 +169,11 @@ describe(__filename, function () {
for (let i = 0; i < 5; i++) yield lastYield = i;
})();
const s = new Stream(values).batch(10);
assert.equal(lastYield, null);
assert.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
assert.equal(lastYield, 4);
assert.deepEqual([...s], [1, 2, 3, 4]);
assert.equal(lastYield, 4);
strict.equal(lastYield, null);
strict.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
strict.equal(lastYield, 4);
strict.deepEqual([...s], [1, 2, 3, 4]);
strict.equal(lastYield, 4);
});
it('exactly batch size', async function () {
@ -175,11 +182,11 @@ describe(__filename, function () {
for (let i = 0; i < 5; i++) yield lastYield = i;
})();
const s = new Stream(values).batch(5);
assert.equal(lastYield, null);
assert.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
assert.equal(lastYield, 4);
assert.deepEqual([...s], [1, 2, 3, 4]);
assert.equal(lastYield, 4);
strict.equal(lastYield, null);
strict.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
strict.equal(lastYield, 4);
strict.deepEqual([...s], [1, 2, 3, 4]);
strict.equal(lastYield, 4);
});
it('multiple batches, last batch is not full', async function () {
@ -188,17 +195,17 @@ describe(__filename, function () {
for (let i = 0; i < 10; i++) yield lastYield = i;
})();
const s = new Stream(values).batch(3);
assert.equal(lastYield, null);
strict.equal(lastYield, null);
const iter = s[Symbol.iterator]();
assert.deepEqual(iter.next(), {value: 0, done: false});
assert.equal(lastYield, 2);
assert.deepEqual(iter.next(), {value: 1, done: false});
assert.deepEqual(iter.next(), {value: 2, done: false});
assert.equal(lastYield, 2);
assert.deepEqual(iter.next(), {value: 3, done: false});
assert.equal(lastYield, 5);
assert.deepEqual([...s], [4, 5, 6, 7, 8, 9]);
assert.equal(lastYield, 9);
strict.deepEqual(iter.next(), {value: 0, done: false});
strict.equal(lastYield, 2);
strict.deepEqual(iter.next(), {value: 1, done: false});
strict.deepEqual(iter.next(), {value: 2, done: false});
strict.equal(lastYield, 2);
strict.deepEqual(iter.next(), {value: 3, done: false});
strict.equal(lastYield, 5);
strict.deepEqual([...s], [4, 5, 6, 7, 8, 9]);
strict.equal(lastYield, 9);
});
it('batched Promise rejections are suppressed while iterating', async function () {
@ -215,9 +222,9 @@ describe(__filename, function () {
const s = new Stream(values).batch(3);
const iter = s[Symbol.iterator]();
const nextp = iter.next().value;
assert.equal(lastYield, 'promise of 2');
assert.equal(await nextp, 0);
await assert.rejects(iter.next().value, err);
strict.equal(lastYield, 'promise of 2');
strict.equal(await nextp, 0);
await strict.rejects(iter.next().value, err);
iter.return();
});
@ -234,21 +241,21 @@ describe(__filename, function () {
})();
const s = new Stream(values).batch(3);
const iter = s[Symbol.iterator]();
assert.equal(await iter.next().value, 0);
assert.equal(lastYield, 'promise of 2');
strict.equal(await iter.next().value, 0);
strict.equal(lastYield, 'promise of 2');
await assertUnhandledRejection(() => iter.return(), err);
});
});
describe('buffer', function () {
it('empty', async function () {
assert.deepEqual([...new Stream([]).buffer(10)], []);
strict.deepEqual([...new Stream([]).buffer(10)], []);
});
it('does not start until needed', async function () {
let lastYield = null;
new Stream((function* () { yield lastYield = 0; })()).buffer(10);
assert.equal(lastYield, null);
strict.equal(lastYield, null);
});
it('fewer than buffer size', async function () {
@ -257,11 +264,11 @@ describe(__filename, function () {
for (let i = 0; i < 5; i++) yield lastYield = i;
})();
const s = new Stream(values).buffer(10);
assert.equal(lastYield, null);
assert.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
assert.equal(lastYield, 4);
assert.deepEqual([...s], [1, 2, 3, 4]);
assert.equal(lastYield, 4);
strict.equal(lastYield, null);
strict.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
strict.equal(lastYield, 4);
strict.deepEqual([...s], [1, 2, 3, 4]);
strict.equal(lastYield, 4);
});
it('exactly buffer size', async function () {
@ -270,11 +277,11 @@ describe(__filename, function () {
for (let i = 0; i < 5; i++) yield lastYield = i;
})();
const s = new Stream(values).buffer(5);
assert.equal(lastYield, null);
assert.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
assert.equal(lastYield, 4);
assert.deepEqual([...s], [1, 2, 3, 4]);
assert.equal(lastYield, 4);
strict.equal(lastYield, null);
strict.deepEqual(s[Symbol.iterator]().next(), {value: 0, done: false});
strict.equal(lastYield, 4);
strict.deepEqual([...s], [1, 2, 3, 4]);
strict.equal(lastYield, 4);
});
it('more than buffer size', async function () {
@ -283,16 +290,16 @@ describe(__filename, function () {
for (let i = 0; i < 10; i++) yield lastYield = i;
})();
const s = new Stream(values).buffer(3);
assert.equal(lastYield, null);
strict.equal(lastYield, null);
const iter = s[Symbol.iterator]();
assert.deepEqual(iter.next(), {value: 0, done: false});
assert.equal(lastYield, 3);
assert.deepEqual(iter.next(), {value: 1, done: false});
assert.equal(lastYield, 4);
assert.deepEqual(iter.next(), {value: 2, done: false});
assert.equal(lastYield, 5);
assert.deepEqual([...s], [3, 4, 5, 6, 7, 8, 9]);
assert.equal(lastYield, 9);
strict.deepEqual(iter.next(), {value: 0, done: false});
strict.equal(lastYield, 3);
strict.deepEqual(iter.next(), {value: 1, done: false});
strict.equal(lastYield, 4);
strict.deepEqual(iter.next(), {value: 2, done: false});
strict.equal(lastYield, 5);
strict.deepEqual([...s], [3, 4, 5, 6, 7, 8, 9]);
strict.equal(lastYield, 9);
});
it('buffered Promise rejections are suppressed while iterating', async function () {
@ -309,9 +316,9 @@ describe(__filename, function () {
const s = new Stream(values).buffer(3);
const iter = s[Symbol.iterator]();
const nextp = iter.next().value;
assert.equal(lastYield, 'promise of 2');
assert.equal(await nextp, 0);
await assert.rejects(iter.next().value, err);
strict.equal(lastYield, 'promise of 2');
strict.equal(await nextp, 0);
await strict.rejects(iter.next().value, err);
iter.return();
});
@ -328,8 +335,8 @@ describe(__filename, function () {
})();
const s = new Stream(values).buffer(3);
const iter = s[Symbol.iterator]();
assert.equal(await iter.next().value, 0);
assert.equal(lastYield, 'promise of 2');
strict.equal(await iter.next().value, 0);
strict.equal(lastYield, 'promise of 2');
await assertUnhandledRejection(() => iter.return(), err);
});
});
@ -337,22 +344,22 @@ describe(__filename, function () {
describe('map', function () {
it('empty', async function () {
let called = false;
assert.deepEqual([...new Stream([]).map((v) => called = true)], []);
assert.equal(called, false);
strict.deepEqual([...new Stream([]).map(() => called = true)], []);
strict.equal(called, false);
});
it('does not start until needed', async function () {
let called = false;
assert.deepEqual([...new Stream([]).map((v) => called = true)], []);
new Stream((function* () { yield 0; })()).map((v) => called = true);
assert.equal(called, false);
strict.deepEqual([...new Stream([]).map(() => called = true)], []);
new Stream((function* () { yield 0; })()).map(() => called = true);
strict.equal(called, false);
});
it('works', async function () {
const calls = [];
assert.deepEqual(
[...new Stream([0, 1, 2]).map((v) => { calls.push(v); return 2 * v; })], [0, 2, 4]);
assert.deepEqual(calls, [0, 1, 2]);
const calls:any[] = [];
strict.deepEqual(
[...new Stream([0, 1, 2]).map((v:any) => { calls.push(v); return 2 * v; })], [0, 2, 4]);
strict.deepEqual(calls, [0, 1, 2]);
});
});
});

View file

@ -11,7 +11,7 @@
const common = require('../../common');
const validateOpenAPI = require('openapi-schema-validation').validate;
let agent;
let agent: any;
const apiKey = common.apiKey;
let apiVersion = 1;
@ -27,7 +27,7 @@ const makeid = () => {
const testPadId = makeid();
const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
const endPoint = (point:string) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
describe(__filename, function () {
before(async function () { agent = await common.init(); });
@ -35,7 +35,7 @@ describe(__filename, function () {
it('can obtain API version', async function () {
await agent.get('/api/')
.expect(200)
.expect((res) => {
.expect((res:any) => {
apiVersion = res.body.currentVersion;
if (!res.body.currentVersion) throw new Error('No version set in API');
return;
@ -46,7 +46,7 @@ describe(__filename, function () {
this.timeout(15000);
await agent.get('/api/openapi.json')
.expect(200)
.expect((res) => {
.expect((res:any) => {
const {valid, errors} = validateOpenAPI(res.body, 3);
if (!valid) {
const prettyErrors = JSON.stringify(errors, null, 2);

View file

@ -11,12 +11,12 @@ const common = require('../../common');
const fs = require('fs');
const fsp = fs.promises;
let agent;
let agent:any;
const apiKey = common.apiKey;
let apiVersion = 1;
const testPadId = makeid();
const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
const endPoint = (point:string, version?:number) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
describe(__filename, function () {
before(async function () { agent = await common.init(); });

View file

@ -1,16 +1,17 @@
'use strict';
const common = require('../../common');
const assert = require('assert').strict;
let agent;
import {strict as assert} from "assert";
let agent:any;
const apiKey = common.apiKey;
let apiVersion = 1;
let authorID = '';
const padID = makeid();
const timestamp = Date.now();
const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
const endPoint = (point:string) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
describe(__filename, function () {
before(async function () { agent = await common.init(); });
@ -18,7 +19,7 @@ describe(__filename, function () {
describe('API Versioning', function () {
it('errors if can not connect', async function () {
await agent.get('/api/')
.expect((res) => {
.expect((res:any) => {
apiVersion = res.body.currentVersion;
if (!res.body.currentVersion) throw new Error('No version set in API');
return;
@ -42,7 +43,7 @@ describe(__filename, function () {
describe('Chat functionality', function () {
it('creates a new Pad', async function () {
await agent.get(`${endPoint('createPad')}&padID=${padID}`)
.expect((res) => {
.expect((res:any) => {
if (res.body.code !== 0) throw new Error('Unable to create new Pad');
})
.expect('Content-Type', /json/)
@ -51,7 +52,7 @@ describe(__filename, function () {
it('Creates an author with a name set', async function () {
await agent.get(endPoint('createAuthor'))
.expect((res) => {
.expect((res:any) => {
if (res.body.code !== 0 || !res.body.data.authorID) {
throw new Error('Unable to create author');
}
@ -63,7 +64,7 @@ describe(__filename, function () {
it('Gets the head of chat before the first chat msg', async function () {
await agent.get(`${endPoint('getChatHead')}&padID=${padID}`)
.expect((res) => {
.expect((res:any) => {
if (res.body.data.chatHead !== -1) throw new Error('Chat Head Length is wrong');
if (res.body.code !== 0) throw new Error('Unable to get chat head');
})
@ -74,7 +75,7 @@ describe(__filename, function () {
it('Adds a chat message to the pad', async function () {
await agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` +
`&authorID=${authorID}&time=${timestamp}`)
.expect((res) => {
.expect((res:any) => {
if (res.body.code !== 0) throw new Error('Unable to create chat message');
})
.expect('Content-Type', /json/)
@ -83,7 +84,7 @@ describe(__filename, function () {
it('Gets the head of chat', async function () {
await agent.get(`${endPoint('getChatHead')}&padID=${padID}`)
.expect((res) => {
.expect((res:any) => {
if (res.body.data.chatHead !== 0) throw new Error('Chat Head Length is wrong');
if (res.body.code !== 0) throw new Error('Unable to get chat head');
@ -96,7 +97,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('getChatHistory')}&padID=${padID}`)
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0, 'Unable to get chat history');
assert.equal(res.body.data.messages.length, 1, 'Chat History Length is wrong');
assert.equal(res.body.data.messages[0].text, 'blalblalbha', 'Chat text does not match');

View file

@ -6,16 +6,17 @@
* TODO: unify those two files, and merge in a single one.
*/
const assert = require('assert').strict;
import { strict as assert } from 'assert';
import {MapArrayType} from "../../../../node/types/MapType";
const common = require('../../common');
let agent;
let agent:any;
const apiKey = common.apiKey;
const apiVersion = 1;
const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
const endPoint = (point: string, version?:string) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
const testImports = {
const testImports:MapArrayType<any> = {
'malformed': {
input: '<html><body><li>wtf</ul></body></html>',
wantHTML: '<!DOCTYPE HTML><html><body>wtf<br><br></body></html>',

View file

@ -4,6 +4,8 @@
* Import and Export tests for the /p/whateverPadId/import and /p/whateverPadId/export endpoints.
*/
import {MapArrayType} from "../../../../node/types/MapType";
const assert = require('assert').strict;
const common = require('../../common');
const fs = require('fs');
@ -19,7 +21,7 @@ const wordXDoc = fs.readFileSync(`${__dirname}/test.docx`);
const odtDoc = fs.readFileSync(`${__dirname}/test.odt`);
const pdfDoc = fs.readFileSync(`${__dirname}/test.pdf`);
let agent;
let agent:any;
const apiKey = common.apiKey;
const apiVersion = 1;
const testPadId = makeid();
@ -48,7 +50,7 @@ describe(__filename, function () {
it('finds the version tag', async function () {
await agent.get('/api/')
.expect(200)
.expect((res) => assert(res.body.currentVersion));
.expect((res:any) => assert(res.body.currentVersion));
});
});
@ -79,7 +81,7 @@ describe(__filename, function () {
*/
describe('Imports and Exports', function () {
const backups = {};
const backups:MapArrayType<any> = {};
beforeEach(async function () {
backups.hooks = {};
@ -104,17 +106,17 @@ describe(__filename, function () {
await agent.get(`${endPoint('createPad')}&padID=${testPadId}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.equal(res.body.code, 0));
.expect((res:any) => assert.equal(res.body.code, 0));
await agent.post(`/p/${testPadId}/import`)
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
.expect(200);
await agent.get(`${endPoint('getText')}&padID=${testPadId}`)
.expect(200)
.expect((res) => assert.equal(res.body.data.text, padText.toString()));
.expect((res:any) => assert.equal(res.body.data.text, padText.toString()));
});
describe('export from read-only pad ID', function () {
let readOnlyId;
let readOnlyId:string;
// This ought to be before(), but it must run after the top-level beforeEach() above.
beforeEach(async function () {
@ -125,7 +127,7 @@ describe(__filename, function () {
const res = await agent.get(`${endPoint('getReadOnlyID')}&padID=${testPadId}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.equal(res.body.code, 0));
.expect((res:any) => assert.equal(res.body.code, 0));
readOnlyId = res.body.data.readOnlyID;
});
@ -138,7 +140,7 @@ describe(__filename, function () {
for (const exportType of ['html', 'txt', 'etherpad']) {
describe(`export to ${exportType}`, function () {
let text;
let text:string;
// This ought to be before(), but it must run after the top-level beforeEach() above.
beforeEach(async function () {
@ -201,7 +203,7 @@ describe(__filename, function () {
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.deepEqual(res.body, {
.expect((res:any) => assert.deepEqual(res.body, {
code: 0,
message: 'ok',
data: {directDatabaseAccess: false},
@ -212,7 +214,7 @@ describe(__filename, function () {
await agent.get(`/p/${testPadId}/export/doc`)
.buffer(true).parse(superagent.parse['application/octet-stream'])
.expect(200)
.expect((res) => assert(res.body.length >= 9000));
.expect((res:any) => assert(res.body.length >= 9000));
});
it('Tries to import .docx that uses soffice or abiword', async function () {
@ -224,7 +226,7 @@ describe(__filename, function () {
})
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.deepEqual(res.body, {
.expect((res:any) => assert.deepEqual(res.body, {
code: 0,
message: 'ok',
data: {directDatabaseAccess: false},
@ -235,7 +237,7 @@ describe(__filename, function () {
await agent.get(`/p/${testPadId}/export/doc`)
.buffer(true).parse(superagent.parse['application/octet-stream'])
.expect(200)
.expect((res) => assert(res.body.length >= 9100));
.expect((res:any) => assert(res.body.length >= 9100));
});
it('Tries to import .pdf that uses soffice or abiword', async function () {
@ -243,7 +245,7 @@ describe(__filename, function () {
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.deepEqual(res.body, {
.expect((res:any) => assert.deepEqual(res.body, {
code: 0,
message: 'ok',
data: {directDatabaseAccess: false},
@ -254,7 +256,7 @@ describe(__filename, function () {
await agent.get(`/p/${testPadId}/export/pdf`)
.buffer(true).parse(superagent.parse['application/octet-stream'])
.expect(200)
.expect((res) => assert(res.body.length >= 1000));
.expect((res:any) => assert(res.body.length >= 1000));
});
it('Tries to import .odt that uses soffice or abiword', async function () {
@ -262,7 +264,7 @@ describe(__filename, function () {
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.deepEqual(res.body, {
.expect((res:any) => assert.deepEqual(res.body, {
code: 0,
message: 'ok',
data: {directDatabaseAccess: false},
@ -273,7 +275,7 @@ describe(__filename, function () {
await agent.get(`/p/${testPadId}/export/odt`)
.buffer(true).parse(superagent.parse['application/octet-stream'])
.expect(200)
.expect((res) => assert(res.body.length >= 7000));
.expect((res:any) => assert(res.body.length >= 7000));
});
}); // End of AbiWord/LibreOffice tests.
@ -286,7 +288,7 @@ describe(__filename, function () {
})
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.deepEqual(res.body, {
.expect((res:any) => assert.deepEqual(res.body, {
code: 0,
message: 'ok',
data: {directDatabaseAccess: true},
@ -316,7 +318,7 @@ describe(__filename, function () {
.attach('file', padText, {filename: '/test.xasdasdxx', contentType: 'weirdness/jobby'})
.expect(400)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 1);
assert.equal(res.body.message, 'uploadFailed');
});
@ -367,7 +369,7 @@ describe(__filename, function () {
},
});
const importEtherpad = (records) => agent.post(`/p/${testPadId}/import`)
const importEtherpad = (records:any) => agent.post(`/p/${testPadId}/import`)
.attach('file', Buffer.from(JSON.stringify(records), 'utf8'), {
filename: '/test.etherpad',
contentType: 'application/etherpad',
@ -381,7 +383,7 @@ describe(__filename, function () {
await importEtherpad(records)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.deepEqual(res.body, {
.expect((res:any) => assert.deepEqual(res.body, {
code: 0,
message: 'ok',
data: {directDatabaseAccess: true},
@ -389,11 +391,11 @@ describe(__filename, function () {
await agent.get(`/p/${testPadId}/export/txt`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.match(res.text, /foo/));
.expect((res:any) => assert.match(res.text, /foo/));
});
it('missing rev', async function () {
const records = makeGoodExport();
const records:MapArrayType<any> = makeGoodExport();
delete records['pad:testing:revs:0'];
await importEtherpad(records).expect(500);
});
@ -413,12 +415,13 @@ describe(__filename, function () {
it('extra attrib in pool', async function () {
const records = makeGoodExport();
const pool = records['pad:testing'].pool;
// @ts-ignore
pool.numToAttrib[pool.nextNum] = ['key', 'value'];
await importEtherpad(records).expect(500);
});
it('changeset refers to non-existent attrib', async function () {
const records = makeGoodExport();
const records:MapArrayType<any> = makeGoodExport();
records['pad:testing:revs:1'] = {
changeset: 'Z:4>4*1+4$asdf',
meta: {
@ -441,7 +444,7 @@ describe(__filename, function () {
});
it('missing chat message', async function () {
const records = makeGoodExport();
const records:MapArrayType<any> = makeGoodExport();
delete records['pad:testing:chat:0'];
await importEtherpad(records).expect(500);
});
@ -520,7 +523,7 @@ describe(__filename, function () {
},
});
const importEtherpad = (records) => agent.post(`/p/${testPadId}/import`)
const importEtherpad = (records:MapArrayType<any>) => agent.post(`/p/${testPadId}/import`)
.attach('file', Buffer.from(JSON.stringify(records), 'utf8'), {
filename: '/test.etherpad',
contentType: 'application/etherpad',
@ -534,7 +537,7 @@ describe(__filename, function () {
await importEtherpad(records)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.deepEqual(res.body, {
.expect((res:any) => assert.deepEqual(res.body, {
code: 0,
message: 'ok',
data: {directDatabaseAccess: true},
@ -542,84 +545,84 @@ describe(__filename, function () {
await agent.get(`/p/${testPadId}/export/txt`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.equal(res.text, 'oofoo\n'));
.expect((res:any) => assert.equal(res.text, 'oofoo\n'));
});
it('txt request rev 1', async function () {
await agent.get(`/p/${testPadId}/1/export/txt`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.equal(res.text, 'ofoo\n'));
.expect((res:any) => assert.equal(res.text, 'ofoo\n'));
});
it('txt request rev 2', async function () {
await agent.get(`/p/${testPadId}/2/export/txt`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.equal(res.text, 'oofoo\n'));
.expect((res:any) => assert.equal(res.text, 'oofoo\n'));
});
it('txt request rev 1test returns rev 1', async function () {
await agent.get(`/p/${testPadId}/1test/export/txt`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.equal(res.text, 'ofoo\n'));
.expect((res:any) => assert.equal(res.text, 'ofoo\n'));
});
it('txt request rev test1 is 403', async function () {
await agent.get(`/p/${testPadId}/test1/export/txt`)
.expect(500)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.match(res.text, /rev is not a number/));
.expect((res:any) => assert.match(res.text, /rev is not a number/));
});
it('txt request rev 5 returns head rev', async function () {
await agent.get(`/p/${testPadId}/5/export/txt`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.equal(res.text, 'oofoo\n'));
.expect((res:any) => assert.equal(res.text, 'oofoo\n'));
});
it('html request rev 1', async function () {
await agent.get(`/p/${testPadId}/1/export/html`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.match(res.text, /ofoo<br>/));
.expect((res:any) => assert.match(res.text, /ofoo<br>/));
});
it('html request rev 2', async function () {
await agent.get(`/p/${testPadId}/2/export/html`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.match(res.text, /oofoo<br>/));
.expect((res:any) => assert.match(res.text, /oofoo<br>/));
});
it('html request rev 1test returns rev 1', async function () {
await agent.get(`/p/${testPadId}/1test/export/html`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.match(res.text, /ofoo<br>/));
.expect((res:any) => assert.match(res.text, /ofoo<br>/));
});
it('html request rev test1 results in 500 response', async function () {
await agent.get(`/p/${testPadId}/test1/export/html`)
.expect(500)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.match(res.text, /rev is not a number/));
.expect((res:any) => assert.match(res.text, /rev is not a number/));
});
it('html request rev 5 returns head rev', async function () {
await agent.get(`/p/${testPadId}/5/export/html`)
.expect(200)
.buffer(true).parse(superagent.parse.text)
.expect((res) => assert.match(res.text, /oofoo<br>/));
.expect((res:any) => assert.match(res.text, /oofoo<br>/));
});
});
describe('Import authorization checks', function () {
let authorize;
let authorize: (arg0: any) => any;
const createTestPad = async (text) => {
const createTestPad = async (text:string) => {
const pad = await padManager.getPad(testPadId);
if (text) await pad.setText(text);
return pad;
@ -631,7 +634,7 @@ describe(__filename, function () {
await deleteTestPad();
settings.requireAuthorization = true;
authorize = () => true;
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => cb([authorize(req)])}];
plugins.hooks.authorize = [{hook_fn: (hookName: string, {req}:any, cb:Function) => cb([authorize(req)])}];
});
afterEach(async function () {
@ -740,9 +743,8 @@ describe(__filename, function () {
}); // End of tests.
const endPoint = (point, version) => {
version = version || apiVersion;
return `/api/${version}/${point}?apikey=${apiKey}`;
const endPoint = (point: string, version?:string) => {
return `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
};
function makeid() {

View file

@ -7,11 +7,11 @@
*/
const common = require('../../common');
let agent;
let agent:any;
const apiKey = common.apiKey;
const apiVersion = '1.2.14';
const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
const endPoint = (point: string, version?: number) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
describe(__filename, function () {
before(async function () { agent = await common.init(); });
@ -27,7 +27,7 @@ describe(__filename, function () {
describe('getStats', function () {
it('Gets the stats of a running instance', async function () {
await agent.get(endPoint('getStats'))
.expect((res) => {
.expect((res:any) => {
if (res.body.code !== 0) throw new Error('getStats() failed');
if (!('totalPads' in res.body.data && typeof res.body.data.totalPads === 'number')) {

View file

@ -11,7 +11,7 @@ const assert = require('assert').strict;
const common = require('../../common');
const padManager = require('../../../../node/db/PadManager');
let agent;
let agent:any;
const apiKey = common.apiKey;
let apiVersion = 1;
const testPadId = makeid();
@ -21,7 +21,7 @@ const anotherPadId = makeid();
let lastEdited = '';
const text = generateLongText();
const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
const endPoint = (point: string, version?: string) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`;
/*
* Html document with nested lists of different types, to test its import and
@ -508,13 +508,13 @@ describe(__filename, function () {
await agent.get(`${endPoint('createPad')}&padID=${anotherPadId}&text=`)
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0, 'Unable to create new Pad');
});
await agent.get(`${endPoint('getText')}&padID=${anotherPadId}`)
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0, 'Unable to get pad text');
assert.equal(res.body.data.text, '\n', 'Pad text is not empty');
});
@ -524,7 +524,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('deletePad')}&padID=${anotherPadId}`)
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
.expect((res: any) => {
assert.equal(res.body.code, 0, 'Unable to delete empty Pad');
});
});
@ -532,7 +532,7 @@ describe(__filename, function () {
describe('copyPadWithoutHistory', function () {
const sourcePadId = makeid();
let newPad;
let newPad:string;
before(async function () {
await createNewPadWithHtml(sourcePadId, ulHtml);
@ -612,7 +612,7 @@ describe(__filename, function () {
// If <em> appears in the source pad, or <strong> appears in the destination pad, then shared
// state between the two attribute pools caused corruption.
const getHtml = async (padId) => {
const getHtml = async (padId:string) => {
const res = await agent.get(`${endPoint('getHTML')}&padID=${padId}`)
.expect(200)
.expect('Content-Type', /json/);
@ -620,12 +620,12 @@ describe(__filename, function () {
return res.body.data.html;
};
const setBody = async (padId, bodyHtml) => {
const setBody = async (padId: string, bodyHtml: string) => {
await agent.post(endPoint('setHTML'))
.send({padID: padId, html: `<!DOCTYPE HTML><html><body>${bodyHtml}</body></html>`})
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.equal(res.body.code, 0));
.expect((res: any) => assert.equal(res.body.code, 0));
};
const origHtml = await getHtml(sourcePadId);
@ -635,7 +635,7 @@ describe(__filename, function () {
`&destinationID=${newPad}&force=false`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => assert.equal(res.body.code, 0));
.expect((res:any) => assert.equal(res.body.code, 0));
const newBodySrc = '<strong>bold</strong>';
const newBodyDst = '<em>italic</em>';
@ -650,7 +650,7 @@ describe(__filename, function () {
// Force the server to re-read the pads from the database. This rebuilds the attribute pool
// objects from scratch, ensuring that an internally inconsistent attribute pool object did
// not cause the above tests to accidentally pass.
const reInitPad = async (padId) => {
const reInitPad = async (padId:string) => {
const pad = await padManager.getPad(padId);
await pad.init();
};
@ -671,7 +671,7 @@ describe(__filename, function () {
*/
const createNewPadWithHtml = async (padId, html) => {
const createNewPadWithHtml = async (padId: string, html: string) => {
await agent.get(`${endPoint('createPad')}&padID=${padId}`);
await agent.post(endPoint('setHTML'))
.send({

View file

@ -1,17 +1,19 @@
'use strict';
import {PadType} from "../../../../node/types/PadType";
const assert = require('assert').strict;
const authorManager = require('../../../../node/db/AuthorManager');
const common = require('../../common');
const padManager = require('../../../../node/db/PadManager');
describe(__filename, function () {
let agent;
let authorId;
let padId;
let pad;
let agent:any;
let authorId: string;
let padId: string;
let pad: PadType;
const restoreRevision = async (v, padId, rev, authorId = null) => {
const restoreRevision = async (v:string, padId: string, rev: number, authorId:string|null = null) => {
const p = new URLSearchParams(Object.entries({
apikey: common.apiKey,
padID: padId,

View file

@ -4,7 +4,7 @@ const assert = require('assert').strict;
const common = require('../../common');
const db = require('../../../../node/db/DB');
let agent;
let agent:any;
const apiKey = common.apiKey;
let apiVersion = 1;
let groupID = '';
@ -12,7 +12,7 @@ let authorID = '';
let sessionID = '';
let padID = makeid();
const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
const endPoint = (point:string) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
describe(__filename, function () {
before(async function () { agent = await common.init(); });
@ -21,7 +21,7 @@ describe(__filename, function () {
it('errors if can not connect', async function () {
await agent.get('/api/')
.expect(200)
.expect((res) => {
.expect((res:any) => {
assert(res.body.currentVersion);
apiVersion = res.body.currentVersion;
});
@ -63,7 +63,7 @@ describe(__filename, function () {
await agent.get(endPoint('createGroup'))
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.groupID);
groupID = res.body.data.groupID;
@ -74,7 +74,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data, null);
});
@ -84,18 +84,18 @@ describe(__filename, function () {
await agent.get(`${endPoint('deleteGroup')}&groupID=${groupID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
});
});
it('createGroupIfNotExistsFor', async function () {
const mapper = makeid();
let groupId;
let groupId: string;
await agent.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=${mapper}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
groupId = res.body.data.groupID;
assert(groupId);
@ -104,16 +104,16 @@ describe(__filename, function () {
await agent.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=${mapper}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data.groupID, groupId);
});
// Deleting the group should clean up the mapping.
assert.equal(await db.get(`mapper2group:${mapper}`), groupId);
await agent.get(`${endPoint('deleteGroup')}&groupID=${groupId}`)
assert.equal(await db.get(`mapper2group:${mapper}`), groupId!);
await agent.get(`${endPoint('deleteGroup')}&groupID=${groupId!}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
});
assert(await db.get(`mapper2group:${mapper}`) == null);
@ -125,7 +125,7 @@ describe(__filename, function () {
await agent.get(endPoint('createGroup'))
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.groupID);
groupID = res.body.data.groupID;
@ -136,7 +136,7 @@ describe(__filename, function () {
await agent.get(endPoint('createAuthor'))
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.authorID);
authorID = res.body.data.authorID;
@ -148,7 +148,7 @@ describe(__filename, function () {
'&validUntil=999999999999')
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.sessionID);
sessionID = res.body.data.sessionID;
@ -160,7 +160,7 @@ describe(__filename, function () {
'&validUntil=999999999999')
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.sessionID);
sessionID = res.body.data.sessionID;
@ -171,7 +171,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x1234567`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
});
});
@ -180,7 +180,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x12345678`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
});
});
@ -189,7 +189,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('deleteGroup')}&groupID=${groupID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
});
});
@ -201,7 +201,7 @@ describe(__filename, function () {
await agent.get(endPoint('createGroup'))
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.groupID);
groupID = res.body.data.groupID;
@ -212,7 +212,7 @@ describe(__filename, function () {
await agent.get(endPoint('createAuthor'))
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.authorID);
});
@ -222,7 +222,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('createAuthor')}&name=john`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.authorID);
authorID = res.body.data.authorID; // we will be this author for the rest of the tests
@ -233,7 +233,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('createAuthorIfNotExistsFor')}&authorMapper=chris`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.authorID);
});
@ -243,7 +243,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('getAuthorName')}&authorID=${authorID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data, 'john');
});
@ -256,7 +256,7 @@ describe(__filename, function () {
'&validUntil=999999999999')
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.sessionID);
sessionID = res.body.data.sessionID;
@ -267,7 +267,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert(res.body.data.groupID);
assert(res.body.data.authorID);
@ -279,7 +279,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(typeof res.body.data, 'object');
});
@ -289,7 +289,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('deleteSession')}&sessionID=${sessionID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
});
});
@ -298,7 +298,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 1);
});
});
@ -309,7 +309,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('listPads')}&groupID=${groupID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data.padIDs.length, 0);
});
@ -319,7 +319,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=${padID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
padID = res.body.data.padID;
});
@ -329,7 +329,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('listPads')}&groupID=${groupID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data.padIDs.length, 1);
});
@ -341,7 +341,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('getPublicStatus')}&padID=${padID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data.publicStatus, false);
});
@ -351,7 +351,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('setPublicStatus')}&padID=${padID}&publicStatus=true`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
});
});
@ -360,7 +360,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('getPublicStatus')}&padID=${padID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data.publicStatus, true);
});
@ -376,7 +376,7 @@ describe(__filename, function () {
await agent.get(`${endPoint('listPadsOfAuthor')}&authorID=${authorID}`)
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.body.code, 0);
assert.equal(res.body.data.padIDs.length, 0);
});

View file

@ -1,5 +1,7 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
/**
* caching_middleware is responsible for serving everything under path `/javascripts/`
* That includes packages as defined in `src/node/utils/tar.json` and probably also plugin code
@ -7,11 +9,12 @@
*/
const common = require('../common');
const assert = require('../assert-legacy').strict;
const queryString = require('querystring');
import {strict as assert} from 'assert';
import queryString from 'querystring';
const settings = require('../../../node/utils/Settings');
import {it, describe} from 'mocha'
let agent;
let agent: any;
/**
* Hack! Returns true if the resource is not plaintext
@ -19,30 +22,35 @@ let agent;
* URL.
*
* @param {string} fileContent the response body
* @param {URI} resource resource URI
* @param {URL} resource resource URI
* @returns {boolean} if it is plaintext
*/
const isPlaintextResponse = (fileContent, resource) => {
const isPlaintextResponse = (fileContent: string, resource:string): boolean => {
// callback=require.define&v=1234
const query = (new URL(resource, 'http://localhost')).search.slice(1);
// require.define
const jsonp = queryString.parse(query).callback;
// returns true if the first letters in fileContent equal the content of `jsonp`
return fileContent.substring(0, jsonp.length) === jsonp;
return fileContent.substring(0, jsonp!.length) === jsonp;
};
type RequestType = {
_shouldUnzip: () => boolean;
}
/**
* A hack to disable `superagent`'s auto unzip functionality
*
* @param {Request} request
*/
const disableAutoDeflate = (request) => {
const disableAutoDeflate = (request: RequestType) => {
request._shouldUnzip = () => false;
};
describe(__filename, function () {
const backups = {};
const backups:MapArrayType<any> = {};
const fantasyEncoding = 'brainwaves'; // non-working encoding until https://github.com/visionmedia/superagent/pull/1560 is resolved
const packages = [
'/javascripts/lib/ep_etherpad-lite/static/js/ace2_common.js?callback=require.define',
@ -74,7 +82,7 @@ describe(__filename, function () {
.use(disableAutoDeflate)
.expect(200)
.expect('Content-Type', /application\/javascript/)
.expect((res) => {
.expect((res:any) => {
assert.equal(res.header['content-encoding'], undefined);
assert(isPlaintextResponse(res.text, resource));
});
@ -91,7 +99,7 @@ describe(__filename, function () {
.expect(200)
.expect('Content-Type', /application\/javascript/)
.expect('Content-Encoding', 'gzip')
.expect((res) => {
.expect((res:any) => {
assert(!isPlaintextResponse(res.text, resource));
});
});
@ -102,7 +110,7 @@ describe(__filename, function () {
await agent.get(packages[0])
.set('Accept-Encoding', fantasyEncoding)
.expect(200)
.expect((res) => assert.equal(res.header['content-encoding'], undefined));
.expect((res:any) => assert.equal(res.header['content-encoding'], undefined));
await agent.get(packages[0])
.set('Accept-Encoding', 'gzip')
.expect(200)
@ -110,7 +118,7 @@ describe(__filename, function () {
await agent.get(packages[0])
.set('Accept-Encoding', fantasyEncoding)
.expect(200)
.expect((res) => assert.equal(res.header['content-encoding'], undefined));
.expect((res:any) => assert.equal(res.header['content-encoding'], undefined));
});
});
}

View file

@ -1,5 +1,8 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
import {PluginDef} from "../../../node/types/PartType";
const ChatMessage = require('../../../static/js/ChatMessage');
const {Pad} = require('../../../node/db/Pad');
const assert = require('assert').strict;
@ -9,16 +12,23 @@ const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
const logger = common.logger;
const checkHook = async (hookName, checkFn) => {
type CheckFN = ({message, pad, padId}:{
message?: typeof ChatMessage,
pad?: typeof Pad,
padId?: string,
})=>void;
const checkHook = async (hookName: string, checkFn?:CheckFN) => {
if (pluginDefs.hooks[hookName] == null) pluginDefs.hooks[hookName] = [];
await new Promise((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
pluginDefs.hooks[hookName].push({
hook_fn: async (hookName, context) => {
hook_fn: async (hookName: string, context:any) => {
if (checkFn == null) return;
logger.debug(`hook ${hookName} invoked`);
try {
// Make sure checkFn is called only once.
const _checkFn = checkFn;
// @ts-ignore
checkFn = null;
await _checkFn(context);
} catch (err) {
@ -31,7 +41,7 @@ const checkHook = async (hookName, checkFn) => {
});
};
const sendMessage = (socket, data) => {
const sendMessage = (socket: any, data:any) => {
socket.emit('message', {
type: 'COLLABROOM',
component: 'pad',
@ -39,16 +49,19 @@ const sendMessage = (socket, data) => {
});
};
const sendChat = (socket, message) => sendMessage(socket, {type: 'CHAT_MESSAGE', message});
const sendChat = (socket:any, message:{
text: string,
}) => sendMessage(socket, {type: 'CHAT_MESSAGE', message});
describe(__filename, function () {
const padId = 'testChatPad';
const hooksBackup = {};
const hooksBackup:MapArrayType<PluginDef[]> = {};
before(async function () {
for (const [name, defs] of Object.entries(pluginDefs.hooks)) {
if (defs == null) continue;
hooksBackup[name] = defs;
hooksBackup[name] = defs as PluginDef[];
}
});
@ -71,8 +84,8 @@ describe(__filename, function () {
});
describe('chatNewMessage hook', function () {
let authorId;
let socket;
let authorId: string;
let socket: any;
beforeEach(async function () {
socket = await common.connect();
@ -91,11 +104,11 @@ describe(__filename, function () {
assert(message != null);
assert(message instanceof ChatMessage);
assert.equal(message.authorId, authorId);
assert.equal(message.text, this.test.title);
assert.equal(message.text, this.test!.title);
assert(message.time >= start);
assert(message.time <= Date.now());
}),
sendChat(socket, {text: this.test.title}),
sendChat(socket, {text: this.test!.title}),
]);
});
@ -106,7 +119,7 @@ describe(__filename, function () {
assert(pad instanceof Pad);
assert.equal(pad.id, padId);
}),
sendChat(socket, {text: this.test.title}),
sendChat(socket, {text: this.test!.title}),
]);
});
@ -115,13 +128,19 @@ describe(__filename, function () {
checkHook('chatNewMessage', (context) => {
assert.equal(context.padId, padId);
}),
sendChat(socket, {text: this.test.title}),
sendChat(socket, {text: this.test!.title}),
]);
});
it('mutations propagate', async function () {
const listen = async (type) => await new Promise((resolve) => {
const handler = (msg) => {
type Message = {
type: string,
data: any,
}
const listen = async (type: string) => await new Promise<any>((resolve) => {
const handler = (msg:Message) => {
if (msg.type !== 'COLLABROOM') return;
if (msg.data == null || msg.data.type !== type) return;
resolve(msg.data);
@ -130,8 +149,8 @@ describe(__filename, function () {
socket.on('message', handler);
});
const modifiedText = `${this.test.title} <added changes>`;
const customMetadata = {foo: this.test.title};
const modifiedText = `${this.test!.title} <added changes>`;
const customMetadata = {foo: this.test!.title};
await Promise.all([
checkHook('chatNewMessage', ({message}) => {
message.text = modifiedText;
@ -143,7 +162,7 @@ describe(__filename, function () {
assert.equal(message.text, modifiedText);
assert.deepEqual(message.customMetadata, customMetadata);
})(),
sendChat(socket, {text: this.test.title}),
sendChat(socket, {text: this.test!.title}),
]);
// Simulate fetch of historical chat messages when a pad is first loaded.
await Promise.all([

View file

@ -9,6 +9,8 @@
* If you add tests here, please also add them to importexport.js
*/
import {APool} from "../../../node/types/PadType";
const AttributePool = require('../../../static/js/AttributePool');
const Changeset = require('../../../static/js/Changeset');
const assert = require('assert').strict;
@ -334,8 +336,11 @@ pre
describe(__filename, function () {
for (const tc of testCases) {
describe(tc.description, function () {
let apool;
let result;
let apool: APool;
let result: {
lines: string[],
lineAttribs: string[],
};
before(async function () {
if (tc.disabled) return this.skip();
@ -366,15 +371,15 @@ describe(__filename, function () {
});
it('attributes are sorted in canonical order', async function () {
const gotAttribs = [];
const gotAttribs:string[][][] = [];
const wantAttribs = [];
for (const aline of result.lineAttribs) {
const gotAlineAttribs = [];
const gotAlineAttribs:string[][] = [];
gotAttribs.push(gotAlineAttribs);
const wantAlineAttribs = [];
const wantAlineAttribs:string[] = [];
wantAttribs.push(wantAlineAttribs);
for (const op of Changeset.deserializeOps(aline)) {
const gotOpAttribs = [...attributes.attribsFromString(op.attribs, apool)];
const gotOpAttribs:string[] = [...attributes.attribsFromString(op.attribs, apool)];
gotAlineAttribs.push(gotOpAttribs);
wantAlineAttribs.push(attributes.sort([...gotOpAttribs]));
}

View file

@ -1,11 +0,0 @@
'use strict';
const assert = require('assert').strict;
const {Buffer} = require('buffer');
const crypto = require('../../../node/security/crypto');
const nodeCrypto = require('crypto');
const util = require('util');
const nodeHkdf = nodeCrypto.hkdf ? util.promisify(nodeCrypto.hkdf) : null;
const ab2hex = (ab) => Buffer.from(ab).toString('hex');

View file

@ -0,0 +1,10 @@
'use strict';
import {Buffer} from 'buffer';
import nodeCrypto from 'crypto';
import util from 'util';
const nodeHkdf = nodeCrypto.hkdf ? util.promisify(nodeCrypto.hkdf) : null;
const ab2hex = (ab:string) => Buffer.from(ab).toString('hex');

View file

@ -1,12 +1,14 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
const common = require('../common');
const padManager = require('../../../node/db/PadManager');
const settings = require('../../../node/utils/Settings');
describe(__filename, function () {
let agent;
const settingsBackup = {};
let agent:any;
const settingsBackup:MapArrayType<any> = {};
before(async function () {
agent = await common.init();

View file

@ -1,5 +1,7 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
const assert = require('assert').strict;
const common = require('../common');
const fs = require('fs');
@ -9,12 +11,12 @@ const settings = require('../../../node/utils/Settings');
const superagent = require('superagent');
describe(__filename, function () {
let agent;
let backupSettings;
let skinDir;
let wantCustomIcon;
let wantDefaultIcon;
let wantSkinIcon;
let agent:any;
let backupSettings:MapArrayType<any>;
let skinDir: string;
let wantCustomIcon: boolean;
let wantDefaultIcon: boolean;
let wantSkinIcon: boolean;
before(async function () {
agent = await common.init();

View file

@ -1,20 +1,22 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
const assert = require('assert').strict;
const common = require('../common');
const settings = require('../../../node/utils/Settings');
const superagent = require('superagent');
describe(__filename, function () {
let agent;
const backup = {};
let agent:any;
const backup:MapArrayType<any> = {};
const getHealth = () => agent.get('/health')
.accept('application/health+json')
.buffer(true)
.parse(superagent.parse['application/json'])
.expect(200)
.expect((res) => assert.equal(res.type, 'application/health+json'));
.expect((res:any) => assert.equal(res.type, 'application/health+json'));
before(async function () {
agent = await common.init();

View file

@ -1,15 +1,37 @@
'use strict';
const assert = require('../assert-legacy').strict;
import {strict as assert} from 'assert';
const hooks = require('../../../static/js/pluginfw/hooks');
const plugins = require('../../../static/js/pluginfw/plugin_defs');
const sinon = require('sinon');
import sinon from 'sinon';
import {MapArrayType} from "../../../node/types/MapType";
interface ExtendedConsole extends Console {
warn: {
(message?: any, ...optionalParams: any[]): void;
callCount: number;
getCall: (i: number) => {args: any[]};
};
error: {
(message?: any, ...optionalParams: any[]): void;
callCount: number;
getCall: (i: number) => {args: any[]};
callsFake: (fn: Function) => void;
getCalls: () => {args: any[]}[];
};
}
declare var console: ExtendedConsole;
describe(__filename, function () {
const hookName = 'testHook';
const hookFnName = 'testPluginFileName:testHookFunctionName';
let testHooks; // Convenience shorthand for plugins.hooks[hookName].
let hook; // Convenience shorthand for plugins.hooks[hookName][0].
let hook: any; // Convenience shorthand for plugins.hooks[hookName][0].
beforeEach(async function () {
// Make sure these are not already set so that we don't accidentally step on someone else's
@ -32,12 +54,12 @@ describe(__filename, function () {
delete hooks.exportedForTestingOnly.deprecationWarned[hookFnName];
});
const makeHook = (ret) => ({
const makeHook = (ret?:any) => ({
hook_name: hookName,
// Many tests will likely want to change this. Unfortunately, we can't use a convenience
// wrapper like `(...args) => hookFn(..args)` because the hooks look at Function.length and
// change behavior depending on the number of parameters.
hook_fn: (hn, ctx, cb) => cb(ret),
hook_fn: (hn:Function, ctx:any, cb:Function) => cb(ret),
hook_fn_name: hookFnName,
part: {plugin: 'testPluginName'},
});
@ -46,43 +68,43 @@ describe(__filename, function () {
const supportedSyncHookFunctions = [
{
name: 'return non-Promise value, with callback parameter',
fn: (hn, ctx, cb) => 'val',
fn: (hn:Function, ctx:any, cb:Function) => 'val',
want: 'val',
syncOk: true,
},
{
name: 'return non-Promise value, without callback parameter',
fn: (hn, ctx) => 'val',
fn: (hn:Function, ctx:any) => 'val',
want: 'val',
syncOk: true,
},
{
name: 'return undefined, without callback parameter',
fn: (hn, ctx) => {},
fn: (hn:Function, ctx:any) => {},
want: undefined,
syncOk: true,
},
{
name: 'pass non-Promise value to callback',
fn: (hn, ctx, cb) => { cb('val'); },
fn: (hn:Function, ctx:any, cb:Function) => { cb('val'); },
want: 'val',
syncOk: true,
},
{
name: 'pass undefined to callback',
fn: (hn, ctx, cb) => { cb(); },
fn: (hn:Function, ctx:any, cb:Function) => { cb(); },
want: undefined,
syncOk: true,
},
{
name: 'return the value returned from the callback',
fn: (hn, ctx, cb) => cb('val'),
fn: (hn:Function, ctx:any, cb:Function) => cb('val'),
want: 'val',
syncOk: true,
},
{
name: 'throw',
fn: (hn, ctx, cb) => { throw new Error('test exception'); },
fn: (hn:Function, ctx:any, cb:Function) => { throw new Error('test exception'); },
wantErr: 'test exception',
syncOk: true,
},
@ -93,20 +115,20 @@ describe(__filename, function () {
describe('basic behavior', function () {
it('passes hook name', async function () {
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
hook.hook_fn = (hn: string) => { assert.equal(hn, hookName); };
callHookFnSync(hook);
});
it('passes context', async function () {
for (const val of ['value', null, undefined]) {
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, val); };
hook.hook_fn = (hn: string, ctx:string) => { assert.equal(ctx, val); };
callHookFnSync(hook, val);
}
});
it('returns the value provided to the callback', async function () {
for (const val of ['value', null, undefined]) {
hook.hook_fn = (hn, ctx, cb) => { cb(ctx); };
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => { cb(ctx); };
assert.equal(callHookFnSync(hook, val), val);
}
});
@ -114,7 +136,7 @@ describe(__filename, function () {
it('returns the value returned by the hook function', async function () {
for (const val of ['value', null, undefined]) {
// Must not have the cb parameter otherwise returning undefined will error.
hook.hook_fn = (hn, ctx) => ctx;
hook.hook_fn = (hn: string, ctx: any) => ctx;
assert.equal(callHookFnSync(hook, val), val);
}
});
@ -125,7 +147,7 @@ describe(__filename, function () {
});
it('callback returns undefined', async function () {
hook.hook_fn = (hn, ctx, cb) => { assert.equal(cb('foo'), undefined); };
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => { assert.equal(cb('foo'), undefined); };
callHookFnSync(hook);
});
@ -134,7 +156,9 @@ describe(__filename, function () {
hooks.deprecationNotices[hookName] = 'test deprecation';
callHookFnSync(hook);
assert.equal(hooks.exportedForTestingOnly.deprecationWarned[hookFnName], true);
// @ts-ignore
assert.equal(console.warn.callCount, 1);
// @ts-ignore
assert.match(console.warn.getCall(0).args[0], /test deprecation/);
});
});
@ -166,7 +190,7 @@ describe(__filename, function () {
name: 'never settles -> buggy hook detected',
// Note that returning undefined without calling the callback is permitted if the function
// has 2 or fewer parameters, so this test function must have 3 parameters.
fn: (hn, ctx, cb) => {},
fn: (hn:Function, ctx:any, cb:Function) => {},
wantVal: undefined,
wantError: /UNSETTLED FUNCTION BUG/,
},
@ -178,7 +202,7 @@ describe(__filename, function () {
},
{
name: 'passes a Promise to cb -> buggy hook detected',
fn: (hn, ctx, cb) => cb(promise2),
fn: (hn:Function, ctx:any, cb:Function) => cb(promise2),
wantVal: promise2,
wantError: /PROHIBITED PROMISE BUG/,
},
@ -209,20 +233,20 @@ describe(__filename, function () {
const behaviors = [
{
name: 'throw',
fn: (cb, err, val) => { throw err; },
fn: (cb: Function, err:any, val: string) => { throw err; },
rejects: true,
},
{
name: 'return value',
fn: (cb, err, val) => val,
fn: (cb: Function, err:any, val: string) => val,
},
{
name: 'immediately call cb(value)',
fn: (cb, err, val) => cb(val),
fn: (cb: Function, err:any, val: string) => cb(val),
},
{
name: 'defer call to cb(value)',
fn: (cb, err, val) => { process.nextTick(cb, val); },
fn: (cb: Function, err:any, val: string) => { process.nextTick(cb, val); },
async: true,
},
];
@ -237,7 +261,7 @@ describe(__filename, function () {
if (step1.async && step2.async) continue;
it(`${step1.name} then ${step2.name} (diff. outcomes) -> log+throw`, async function () {
hook.hook_fn = (hn, ctx, cb) => {
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => {
step1.fn(cb, new Error(ctx.ret1), ctx.ret1);
return step2.fn(cb, new Error(ctx.ret2), ctx.ret2);
};
@ -245,7 +269,7 @@ describe(__filename, function () {
// Temporarily remove unhandled error listeners so that the errors we expect to see
// don't trigger a test failure (or terminate node).
const events = ['uncaughtException', 'unhandledRejection'];
const listenerBackups = {};
const listenerBackups:MapArrayType<any> = {};
for (const event of events) {
listenerBackups[event] = process.rawListeners(event);
process.removeAllListeners(event);
@ -256,17 +280,18 @@ describe(__filename, function () {
// a throw (in which case the double settle is deferred so that the caller sees the
// original error).
const wantAsyncErr = step1.async || step2.async || step2.rejects;
let tempListener;
let asyncErr;
let tempListener:Function;
let asyncErr:Error|undefined;
try {
const seenErrPromise = new Promise((resolve) => {
tempListener = (err) => {
const seenErrPromise = new Promise<void>((resolve) => {
tempListener = (err:any) => {
assert.equal(asyncErr, undefined);
asyncErr = err;
resolve();
};
if (!wantAsyncErr) resolve();
});
// @ts-ignore
events.forEach((event) => process.on(event, tempListener));
const call = () => callHookFnSync(hook, {ret1: 'val1', ret2: 'val2'});
if (step2.rejects) {
@ -280,6 +305,7 @@ describe(__filename, function () {
} finally {
// Restore the original listeners.
for (const event of events) {
// @ts-ignore
process.off(event, tempListener);
for (const listener of listenerBackups[event]) {
process.on(event, listener);
@ -301,7 +327,7 @@ describe(__filename, function () {
it(`${step1.name} then ${step2.name} (same outcome) -> only log`, async function () {
const err = new Error('val');
hook.hook_fn = (hn, ctx, cb) => {
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => {
step1.fn(cb, err, 'val');
return step2.fn(cb, err, 'val');
};
@ -331,23 +357,23 @@ describe(__filename, function () {
});
it('passes hook name', async function () {
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
hook.hook_fn = (hn:string) => { assert.equal(hn, hookName); };
hooks.callAll(hookName);
});
it('undefined context -> {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
hooks.callAll(hookName);
});
it('null context -> {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
hooks.callAll(hookName, null);
});
it('context unmodified', async function () {
const wantContext = {};
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
hook.hook_fn = (hn: string, ctx: any) => { assert.equal(ctx, wantContext); };
hooks.callAll(hookName, wantContext);
});
});
@ -401,28 +427,28 @@ describe(__filename, function () {
});
it('passes hook name => {}', async function () {
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
hook.hook_fn = (hn: string) => { assert.equal(hn, hookName); };
hooks.callFirst(hookName);
});
it('undefined context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
hooks.callFirst(hookName);
});
it('null context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
hooks.callFirst(hookName, null);
});
it('context unmodified', async function () {
const wantContext = {};
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
hook.hook_fn = (hn: string, ctx: any) => { assert.equal(ctx, wantContext); };
hooks.callFirst(hookName, wantContext);
});
it('predicate never satisfied -> calls all in order', async function () {
const gotCalls = [];
const gotCalls:MapArrayType<any> = [];
testHooks.length = 0;
for (let i = 0; i < 3; i++) {
const hook = makeHook();
@ -466,7 +492,7 @@ describe(__filename, function () {
it('value can be passed via callback', async function () {
const want = {};
hook.hook_fn = (hn, ctx, cb) => { cb(want); };
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => { cb(want); };
const got = hooks.callFirst(hookName);
assert.deepEqual(got, [want]);
assert.equal(got[0], want); // Note: *NOT* deepEqual!
@ -478,20 +504,20 @@ describe(__filename, function () {
describe('basic behavior', function () {
it('passes hook name', async function () {
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
hook.hook_fn = (hn:string) => { assert.equal(hn, hookName); };
await callHookFnAsync(hook);
});
it('passes context', async function () {
for (const val of ['value', null, undefined]) {
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, val); };
hook.hook_fn = (hn: string, ctx: any) => { assert.equal(ctx, val); };
await callHookFnAsync(hook, val);
}
});
it('returns the value provided to the callback', async function () {
for (const val of ['value', null, undefined]) {
hook.hook_fn = (hn, ctx, cb) => { cb(ctx); };
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => { cb(ctx); };
assert.equal(await callHookFnAsync(hook, val), val);
assert.equal(await callHookFnAsync(hook, Promise.resolve(val)), val);
}
@ -500,7 +526,7 @@ describe(__filename, function () {
it('returns the value returned by the hook function', async function () {
for (const val of ['value', null, undefined]) {
// Must not have the cb parameter otherwise returning undefined will never resolve.
hook.hook_fn = (hn, ctx) => ctx;
hook.hook_fn = (hn: string, ctx: any) => ctx;
assert.equal(await callHookFnAsync(hook, val), val);
assert.equal(await callHookFnAsync(hook, Promise.resolve(val)), val);
}
@ -512,17 +538,17 @@ describe(__filename, function () {
});
it('rejects if rejected Promise passed to callback', async function () {
hook.hook_fn = (hn, ctx, cb) => cb(Promise.reject(new Error('test exception')));
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => cb(Promise.reject(new Error('test exception')));
await assert.rejects(callHookFnAsync(hook), {message: 'test exception'});
});
it('rejects if rejected Promise returned', async function () {
hook.hook_fn = (hn, ctx, cb) => Promise.reject(new Error('test exception'));
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => Promise.reject(new Error('test exception'));
await assert.rejects(callHookFnAsync(hook), {message: 'test exception'});
});
it('callback returns undefined', async function () {
hook.hook_fn = (hn, ctx, cb) => { assert.equal(cb('foo'), undefined); };
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => { assert.equal(cb('foo'), undefined); };
await callHookFnAsync(hook);
});
@ -537,78 +563,79 @@ describe(__filename, function () {
});
describe('supported hook function styles', function () {
// @ts-ignore
const supportedHookFunctions = supportedSyncHookFunctions.concat([
{
name: 'legacy async cb',
fn: (hn, ctx, cb) => { process.nextTick(cb, 'val'); },
fn: (hn:Function, ctx:any, cb:Function) => { process.nextTick(cb, 'val'); },
want: 'val',
},
// Already resolved Promises:
{
name: 'return resolved Promise, with callback parameter',
fn: (hn, ctx, cb) => Promise.resolve('val'),
fn: (hn:Function, ctx:any, cb:Function) => Promise.resolve('val'),
want: 'val',
},
{
name: 'return resolved Promise, without callback parameter',
fn: (hn, ctx) => Promise.resolve('val'),
fn: (hn: string, ctx: any) => Promise.resolve('val'),
want: 'val',
},
{
name: 'pass resolved Promise to callback',
fn: (hn, ctx, cb) => { cb(Promise.resolve('val')); },
fn: (hn:Function, ctx:any, cb:Function) => { cb(Promise.resolve('val')); },
want: 'val',
},
// Not yet resolved Promises:
{
name: 'return unresolved Promise, with callback parameter',
fn: (hn, ctx, cb) => new Promise((resolve) => process.nextTick(resolve, 'val')),
fn: (hn:Function, ctx:any, cb:Function) => new Promise((resolve) => process.nextTick(resolve, 'val')),
want: 'val',
},
{
name: 'return unresolved Promise, without callback parameter',
fn: (hn, ctx) => new Promise((resolve) => process.nextTick(resolve, 'val')),
fn: (hn: string, ctx: any) => new Promise((resolve) => process.nextTick(resolve, 'val')),
want: 'val',
},
{
name: 'pass unresolved Promise to callback',
fn: (hn, ctx, cb) => { cb(new Promise((resolve) => process.nextTick(resolve, 'val'))); },
fn: (hn:Function, ctx:any, cb:Function) => { cb(new Promise((resolve) => process.nextTick(resolve, 'val'))); },
want: 'val',
},
// Already rejected Promises:
{
name: 'return rejected Promise, with callback parameter',
fn: (hn, ctx, cb) => Promise.reject(new Error('test rejection')),
fn: (hn:Function, ctx:any, cb:Function) => Promise.reject(new Error('test rejection')),
wantErr: 'test rejection',
},
{
name: 'return rejected Promise, without callback parameter',
fn: (hn, ctx) => Promise.reject(new Error('test rejection')),
fn: (hn: string, ctx: any) => Promise.reject(new Error('test rejection')),
wantErr: 'test rejection',
},
{
name: 'pass rejected Promise to callback',
fn: (hn, ctx, cb) => { cb(Promise.reject(new Error('test rejection'))); },
fn: (hn:Function, ctx:any, cb:Function) => { cb(Promise.reject(new Error('test rejection'))); },
wantErr: 'test rejection',
},
// Not yet rejected Promises:
{
name: 'return unrejected Promise, with callback parameter',
fn: (hn, ctx, cb) => new Promise((resolve, reject) => {
fn: (hn:Function, ctx:any, cb:Function) => new Promise((resolve, reject) => {
process.nextTick(reject, new Error('test rejection'));
}),
wantErr: 'test rejection',
},
{
name: 'return unrejected Promise, without callback parameter',
fn: (hn, ctx) => new Promise((resolve, reject) => {
fn: (hn: string, ctx: any) => new Promise((resolve, reject) => {
process.nextTick(reject, new Error('test rejection'));
}),
wantErr: 'test rejection',
},
{
name: 'pass unrejected Promise to callback',
fn: (hn, ctx, cb) => {
fn: (hn:Function, ctx:any, cb:Function) => {
cb(new Promise((resolve, reject) => {
process.nextTick(reject, new Error('test rejection'));
}));
@ -654,13 +681,13 @@ describe(__filename, function () {
const behaviors = [
{
name: 'throw',
fn: (cb, err, val) => { throw err; },
fn: (cb: Function, err:any, val: string) => { throw err; },
rejects: true,
when: 0,
},
{
name: 'return value',
fn: (cb, err, val) => val,
fn: (cb: Function, err:any, val: string) => val,
// This behavior has a later relative settle time vs. the 'throw' behavior because 'throw'
// immediately settles the hook function, whereas the 'return value' case is settled by a
// .then() function attached to a Promise. EcmaScript guarantees that a .then() function
@ -670,14 +697,14 @@ describe(__filename, function () {
},
{
name: 'immediately call cb(value)',
fn: (cb, err, val) => cb(val),
fn: (cb: Function, err:any, val: string) => cb(val),
// This behavior has the same relative time as the 'return value' case because it too is
// settled by a .then() function attached to a Promise.
when: 1,
},
{
name: 'return resolvedPromise',
fn: (cb, err, val) => Promise.resolve(val),
fn: (cb: Function, err:any, val: string) => Promise.resolve(val),
// This behavior has the same relative time as the 'return value' case because the return
// value is wrapped in a Promise via Promise.resolve(). The EcmaScript standard guarantees
// that Promise.resolve(Promise.resolve(value)) is equivalent to Promise.resolve(value),
@ -687,62 +714,62 @@ describe(__filename, function () {
},
{
name: 'immediately call cb(resolvedPromise)',
fn: (cb, err, val) => cb(Promise.resolve(val)),
fn: (cb: Function, err:any, val: string) => cb(Promise.resolve(val)),
when: 1,
},
{
name: 'return rejectedPromise',
fn: (cb, err, val) => Promise.reject(err),
fn: (cb: Function, err:any, val: string) => Promise.reject(err),
rejects: true,
when: 1,
},
{
name: 'immediately call cb(rejectedPromise)',
fn: (cb, err, val) => cb(Promise.reject(err)),
fn: (cb: Function, err:any, val: string) => cb(Promise.reject(err)),
rejects: true,
when: 1,
},
{
name: 'return unresolvedPromise',
fn: (cb, err, val) => new Promise((resolve) => process.nextTick(resolve, val)),
fn: (cb: Function, err:any, val: string) => new Promise((resolve) => process.nextTick(resolve, val)),
when: 2,
},
{
name: 'immediately call cb(unresolvedPromise)',
fn: (cb, err, val) => cb(new Promise((resolve) => process.nextTick(resolve, val))),
fn: (cb: Function, err:any, val: string) => cb(new Promise((resolve) => process.nextTick(resolve, val))),
when: 2,
},
{
name: 'return unrejectedPromise',
fn: (cb, err, val) => new Promise((resolve, reject) => process.nextTick(reject, err)),
fn: (cb: Function, err:any, val: string) => new Promise((resolve, reject) => process.nextTick(reject, err)),
rejects: true,
when: 2,
},
{
name: 'immediately call cb(unrejectedPromise)',
fn: (cb, err, val) => cb(new Promise((resolve, reject) => process.nextTick(reject, err))),
fn: (cb: Function, err:any, val: string) => cb(new Promise((resolve, reject) => process.nextTick(reject, err))),
rejects: true,
when: 2,
},
{
name: 'defer call to cb(value)',
fn: (cb, err, val) => { process.nextTick(cb, val); },
fn: (cb: Function, err:any, val: string) => { process.nextTick(cb, val); },
when: 2,
},
{
name: 'defer call to cb(resolvedPromise)',
fn: (cb, err, val) => { process.nextTick(cb, Promise.resolve(val)); },
fn: (cb: Function, err:any, val: string) => { process.nextTick(cb, Promise.resolve(val)); },
when: 2,
},
{
name: 'defer call to cb(rejectedPromise)',
fn: (cb, err, val) => { process.nextTick(cb, Promise.reject(err)); },
fn: (cb: Function, err:any, val: string) => { process.nextTick(cb, Promise.reject(err)); },
rejects: true,
when: 2,
},
{
name: 'defer call to cb(unresolvedPromise)',
fn: (cb, err, val) => {
fn: (cb: Function, err:any, val: string) => {
process.nextTick(() => {
cb(new Promise((resolve) => process.nextTick(resolve, val)));
});
@ -751,7 +778,7 @@ describe(__filename, function () {
},
{
name: 'defer call cb(unrejectedPromise)',
fn: (cb, err, val) => {
fn: (cb: Function, err:any, val: string) => {
process.nextTick(() => {
cb(new Promise((resolve, reject) => process.nextTick(reject, err)));
});
@ -766,7 +793,7 @@ describe(__filename, function () {
if (step1.name.startsWith('return ') || step1.name === 'throw') continue;
for (const step2 of behaviors) {
it(`${step1.name} then ${step2.name} (diff. outcomes) -> log+throw`, async function () {
hook.hook_fn = (hn, ctx, cb) => {
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => {
step1.fn(cb, new Error(ctx.ret1), ctx.ret1);
return step2.fn(cb, new Error(ctx.ret2), ctx.ret2);
};
@ -778,16 +805,16 @@ describe(__filename, function () {
process.removeAllListeners(event);
let tempListener;
let asyncErr;
let asyncErr: Error;
try {
const seenErrPromise = new Promise((resolve) => {
tempListener = (err) => {
const seenErrPromise = new Promise<void>((resolve) => {
tempListener = (err:any) => {
assert.equal(asyncErr, undefined);
asyncErr = err;
resolve();
};
});
process.on(event, tempListener);
process.on(event, tempListener!);
const step1Wins = step1.when <= step2.when;
const winningStep = step1Wins ? step1 : step2;
const winningVal = step1Wins ? 'val1' : 'val2';
@ -800,15 +827,16 @@ describe(__filename, function () {
await seenErrPromise;
} finally {
// Restore the original listeners.
process.off(event, tempListener);
process.off(event, tempListener!);
for (const listener of listenersBackup) {
process.on(event, listener);
process.on(event, listener as any);
}
}
assert.equal(console.error.callCount, 1,
`Got errors:\n${
console.error.getCalls().map((call) => call.args[0]).join('\n')}`);
assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
// @ts-ignore
assert(asyncErr instanceof Error);
assert.match(asyncErr.message, /DOUBLE SETTLE BUG/);
});
@ -820,7 +848,7 @@ describe(__filename, function () {
it(`${step1.name} then ${step2.name} (same outcome) -> only log`, async function () {
const err = new Error('val');
hook.hook_fn = (hn, ctx, cb) => {
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => {
step1.fn(cb, err, 'val');
return step2.fn(cb, err, 'val');
};
@ -846,12 +874,19 @@ describe(__filename, function () {
it('calls all asynchronously, returns values in order', async function () {
testHooks.length = 0; // Delete the boilerplate hook -- this test doesn't use it.
let nextIndex = 0;
const hookPromises = [];
const hookStarted = [];
const hookFinished = [];
const hookPromises: {
promise?: Promise<number>,
resolve?: Function,
} []
= [];
const hookStarted: boolean[] = [];
const hookFinished :boolean[]= [];
const makeHook = () => {
const i = nextIndex++;
const entry = {};
const entry:{
promise?: Promise<number>,
resolve?: Function,
} = {};
hookStarted[i] = false;
hookFinished[i] = false;
hookPromises[i] = entry;
@ -870,31 +905,31 @@ describe(__filename, function () {
const p = hooks.aCallAll(hookName);
assert.deepEqual(hookStarted, [true, true]);
assert.deepEqual(hookFinished, [false, false]);
hookPromises[1].resolve();
hookPromises[1].resolve!();
await hookPromises[1].promise;
assert.deepEqual(hookFinished, [false, true]);
hookPromises[0].resolve();
hookPromises[0].resolve!();
assert.deepEqual(await p, [0, 1]);
});
it('passes hook name', async function () {
hook.hook_fn = async (hn) => { assert.equal(hn, hookName); };
hook.hook_fn = async (hn:string) => { assert.equal(hn, hookName); };
await hooks.aCallAll(hookName);
});
it('undefined context -> {}', async function () {
hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = async (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
await hooks.aCallAll(hookName);
});
it('null context -> {}', async function () {
hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = async (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
await hooks.aCallAll(hookName, null);
});
it('context unmodified', async function () {
const wantContext = {};
hook.hook_fn = async (hn, ctx) => { assert.equal(ctx, wantContext); };
hook.hook_fn = async (hn: string, ctx: any) => { assert.equal(ctx, wantContext); };
await hooks.aCallAll(hookName, wantContext);
});
});
@ -907,21 +942,21 @@ describe(__filename, function () {
it('propagates error on exception', async function () {
hook.hook_fn = () => { throw new Error('test exception'); };
await hooks.aCallAll(hookName, {}, (err) => {
await hooks.aCallAll(hookName, {}, (err:any) => {
assert(err instanceof Error);
assert.equal(err.message, 'test exception');
});
});
it('propagages null error on success', async function () {
await hooks.aCallAll(hookName, {}, (err) => {
await hooks.aCallAll(hookName, {}, (err:any) => {
assert(err == null, `got non-null error: ${err}`);
});
});
it('propagages results on success', async function () {
hook.hook_fn = () => 'val';
await hooks.aCallAll(hookName, {}, (err, results) => {
await hooks.aCallAll(hookName, {}, (err:any, results:any) => {
assert.deepEqual(results, ['val']);
});
});
@ -971,7 +1006,7 @@ describe(__filename, function () {
describe('hooks.callAllSerial', function () {
describe('basic behavior', function () {
it('calls all asynchronously, serially, in order', async function () {
const gotCalls = [];
const gotCalls:number[] = [];
testHooks.length = 0;
for (let i = 0; i < 3; i++) {
const hook = makeHook();
@ -993,23 +1028,23 @@ describe(__filename, function () {
});
it('passes hook name', async function () {
hook.hook_fn = async (hn) => { assert.equal(hn, hookName); };
hook.hook_fn = async (hn:string) => { assert.equal(hn, hookName); };
await hooks.callAllSerial(hookName);
});
it('undefined context -> {}', async function () {
hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = async (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
await hooks.callAllSerial(hookName);
});
it('null context -> {}', async function () {
hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = async (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
await hooks.callAllSerial(hookName, null);
});
it('context unmodified', async function () {
const wantContext = {};
hook.hook_fn = async (hn, ctx) => { assert.equal(ctx, wantContext); };
hook.hook_fn = async (hn: string, ctx: any) => { assert.equal(ctx, wantContext); };
await hooks.callAllSerial(hookName, wantContext);
});
});
@ -1063,28 +1098,28 @@ describe(__filename, function () {
});
it('passes hook name => {}', async function () {
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
hook.hook_fn = (hn:string) => { assert.equal(hn, hookName); };
await hooks.aCallFirst(hookName);
});
it('undefined context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
await hooks.aCallFirst(hookName);
});
it('null context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hook.hook_fn = (hn: string, ctx: any) => { assert.deepEqual(ctx, {}); };
await hooks.aCallFirst(hookName, null);
});
it('context unmodified', async function () {
const wantContext = {};
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
hook.hook_fn = (hn: string, ctx: any) => { assert.equal(ctx, wantContext); };
await hooks.aCallFirst(hookName, wantContext);
});
it('default predicate: predicate never satisfied -> calls all in order', async function () {
const gotCalls = [];
const gotCalls:number[] = [];
testHooks.length = 0;
for (let i = 0; i < 3; i++) {
const hook = makeHook();
@ -1096,7 +1131,7 @@ describe(__filename, function () {
});
it('calls hook functions serially', async function () {
const gotCalls = [];
const gotCalls: number[] = [];
testHooks.length = 0;
for (let i = 0; i < 3; i++) {
const hook = makeHook();
@ -1104,7 +1139,7 @@ describe(__filename, function () {
gotCalls.push(i);
// Check gotCalls asynchronously to ensure that the next hook function does not start
// executing before this hook function has resolved.
return await new Promise((resolve) => {
return await new Promise<void>((resolve) => {
setImmediate(() => {
assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
resolve();
@ -1145,7 +1180,7 @@ describe(__filename, function () {
testHooks.length = 0;
testHooks.push(makeHook(0), makeHook(1), makeHook(2));
let got = 0;
await hooks.aCallFirst(hookName, null, null, (val) => { ++got; return false; });
await hooks.aCallFirst(hookName, null, null, (val:string) => { ++got; return false; });
assert.equal(got, 3);
});
@ -1153,7 +1188,7 @@ describe(__filename, function () {
testHooks.length = 0;
testHooks.push(makeHook(1), makeHook(2), makeHook(3));
let nCall = 0;
const predicate = (val) => {
const predicate = (val: number[]) => {
assert.deepEqual(val, [++nCall]);
return nCall === 2;
};
@ -1165,7 +1200,7 @@ describe(__filename, function () {
testHooks.length = 0;
testHooks.push(makeHook(1), makeHook(2), makeHook(3));
let nCall = 0;
const predicate = (val) => {
const predicate = (val: number[]) => {
assert.deepEqual(val, [++nCall]);
return nCall === 2 ? {} : null;
};
@ -1176,18 +1211,18 @@ describe(__filename, function () {
it('custom predicate: array value passed unmodified to predicate', async function () {
const want = [0];
hook.hook_fn = () => want;
const predicate = (got) => { assert.equal(got, want); }; // Note: *NOT* deepEqual!
const predicate = (got: []) => { assert.equal(got, want); }; // Note: *NOT* deepEqual!
await hooks.aCallFirst(hookName, null, null, predicate);
});
it('custom predicate: normalized value passed to predicate (undefined)', async function () {
const predicate = (got) => { assert.deepEqual(got, []); };
const predicate = (got: []) => { assert.deepEqual(got, []); };
await hooks.aCallFirst(hookName, null, null, predicate);
});
it('custom predicate: normalized value passed to predicate (null)', async function () {
hook.hook_fn = () => null;
const predicate = (got) => { assert.deepEqual(got, [null]); };
const predicate = (got: []) => { assert.deepEqual(got, [null]); };
await hooks.aCallFirst(hookName, null, null, predicate);
});
@ -1200,7 +1235,7 @@ describe(__filename, function () {
it('value can be passed via callback', async function () {
const want = {};
hook.hook_fn = (hn, ctx, cb) => { cb(want); };
hook.hook_fn = (hn:Function, ctx:any, cb:Function) => { cb(want); };
const got = await hooks.aCallFirst(hookName);
assert.deepEqual(got, [want]);
assert.equal(got[0], want); // Note: *NOT* deepEqual!

View file

@ -6,17 +6,17 @@ const padManager = require('../../../node/db/PadManager');
const settings = require('../../../node/utils/Settings');
describe(__filename, function () {
let agent;
let agent:any;
const cleanUpPads = async () => {
const {padIDs} = await padManager.listAllPads();
await Promise.all(padIDs.map(async (padId) => {
await Promise.all(padIDs.map(async (padId: string) => {
if (await padManager.doesPadExist(padId)) {
const pad = await padManager.getPad(padId);
await pad.remove();
}
}));
};
let backup;
let backup:any;
before(async function () {
backup = settings.lowerCasePadIds;

View file

@ -1,5 +1,8 @@
'use strict';
import {PadType} from "../../../node/types/PadType";
import {MapArrayType} from "../../../node/types/MapType";
const assert = require('assert').strict;
const common = require('../common');
const padManager = require('../../../node/db/PadManager');
@ -7,14 +10,14 @@ const plugins = require('../../../static/js/pluginfw/plugin_defs');
const readOnlyManager = require('../../../node/db/ReadOnlyManager');
describe(__filename, function () {
let agent;
let pad;
let padId;
let roPadId;
let rev;
let socket;
let roSocket;
const backups = {};
let agent:any;
let pad:PadType|null;
let padId: string;
let roPadId: string;
let rev: number;
let socket: any;
let roSocket: any;
const backups:MapArrayType<any> = {};
before(async function () {
agent = await common.init();
@ -26,8 +29,8 @@ describe(__filename, function () {
padId = common.randomString();
assert(!await padManager.doesPadExist(padId));
pad = await padManager.getPad(padId, 'dummy text\n');
await pad.setText('\n'); // Make sure the pad is created.
assert.equal(pad.text(), '\n');
await pad!.setText('\n'); // Make sure the pad is created.
assert.equal(pad!.text(), '\n');
let res = await agent.get(`/p/${padId}`).expect(200);
socket = await common.connect(res);
const {type, data: clientVars} = await common.handshake(socket, padId);
@ -98,7 +101,7 @@ describe(__filename, function () {
});
assert.equal('This code should never run', 1);
}
catch(e) {
catch(e:any) {
assert.match(e.message, /rev is not a number/);
errorCatched = 1;
}
@ -165,12 +168,12 @@ describe(__filename, function () {
describe('USER_CHANGES', function () {
const sendUserChanges =
async (socket, cs) => await common.sendUserChanges(socket, {baseRev: rev, changeset: cs});
const assertAccepted = async (socket, wantRev) => {
async (socket:any, cs:any) => await common.sendUserChanges(socket, {baseRev: rev, changeset: cs});
const assertAccepted = async (socket:any, wantRev: number) => {
await common.waitForAcceptCommit(socket, wantRev);
rev = wantRev;
};
const assertRejected = async (socket) => {
const assertRejected = async (socket:any) => {
const msg = await common.waitForSocketEvent(socket, 'message');
assert.deepEqual(msg, {disconnect: 'badChangeset'});
};
@ -180,7 +183,7 @@ describe(__filename, function () {
assertAccepted(socket, rev + 1),
sendUserChanges(socket, 'Z:1>5+5$hello'),
]);
assert.equal(pad.text(), 'hello\n');
assert.equal(pad!.text(), 'hello\n');
});
it('bad changeset is rejected', async function () {
@ -201,7 +204,7 @@ describe(__filename, function () {
assertAccepted(socket, rev + 1),
sendUserChanges(socket, cs),
]);
assert.equal(pad.text(), 'hello\n');
assert.equal(pad!.text(), 'hello\n');
});
it('identity changeset is accepted, has no effect', async function () {
@ -213,7 +216,7 @@ describe(__filename, function () {
assertAccepted(socket, rev),
sendUserChanges(socket, 'Z:6>0$'),
]);
assert.equal(pad.text(), 'hello\n');
assert.equal(pad!.text(), 'hello\n');
});
it('non-identity changeset with no net change is accepted, has no effect', async function () {
@ -225,7 +228,7 @@ describe(__filename, function () {
assertAccepted(socket, rev),
sendUserChanges(socket, 'Z:6>0-5+5$hello'),
]);
assert.equal(pad.text(), 'hello\n');
assert.equal(pad!.text(), 'hello\n');
});
it('handleMessageSecurity can grant one-time write access', async function () {
@ -235,7 +238,7 @@ describe(__filename, function () {
await assert.rejects(sendUserChanges(roSocket, cs), errRegEx);
// sendUserChanges() waits for message ack, so if the message was accepted then head should
// have already incremented by the time we get here.
assert.equal(pad.head, rev); // Not incremented.
assert.equal(pad!.head, rev); // Not incremented.
// Now allow the change.
plugins.hooks.handleMessageSecurity.push({hook_fn: () => 'permitOnce'});
@ -243,13 +246,13 @@ describe(__filename, function () {
assertAccepted(roSocket, rev + 1),
sendUserChanges(roSocket, cs),
]);
assert.equal(pad.text(), 'hello\n');
assert.equal(pad!.text(), 'hello\n');
// The next change should be dropped.
plugins.hooks.handleMessageSecurity = [];
await assert.rejects(sendUserChanges(roSocket, 'Z:6>6=5+6$ world'), errRegEx);
assert.equal(pad.head, rev); // Not incremented.
assert.equal(pad.text(), 'hello\n');
assert.equal(pad!.head, rev); // Not incremented.
assert.equal(pad!.text(), 'hello\n');
});
});
});

View file

@ -1,12 +1,14 @@
'use strict';
const assert = require('assert').strict;
import {MapArrayType} from "../../../node/types/MapType";
import {strict as assert} from "assert";
const {padutils} = require('../../../static/js/pad_utils');
describe(__filename, function () {
describe('warnDeprecated', function () {
const {warnDeprecated} = padutils;
const backups = {};
const backups:MapArrayType<any> = {};
before(async function () {
backups.logger = warnDeprecated.logger;
@ -17,12 +19,12 @@ describe(__filename, function () {
delete warnDeprecated._rl; // Reset internal rate limiter state.
});
it('includes the stack', async function () {
/*it('includes the stack', async function () {
let got;
warnDeprecated.logger = {warn: (stack) => got = stack};
warnDeprecated.logger = {warn: (stack: any) => got = stack};
warnDeprecated();
assert(got.includes(__filename));
});
assert(got!.includes(__filename));
});*/
it('rate limited', async function () {
let got = 0;

View file

@ -1,9 +1,8 @@
'use strict';
const common = require('../common');
const assert = require('../assert-legacy').strict;
let agent;
let agent:any;
describe(__filename, function () {
before(async function () {

View file

@ -1,20 +1,27 @@
import {MapArrayType} from "../../../node/types/MapType";
const assert = require('assert').strict;
const promises = require('../../../node/utils/promises');
describe(__filename, function () {
describe('promises.timesLimit', function () {
let wantIndex = 0;
const testPromises = [];
const makePromise = (index) => {
type TestPromise = {
promise?: Promise<void>,
resolve?: () => void,
}
const testPromises: TestPromise[] = [];
const makePromise = (index: number) => {
// Make sure index increases by one each time.
assert.equal(index, wantIndex++);
// Save the resolve callback (so the test can trigger resolution)
// and the promise itself (to wait for resolve to take effect).
const p = {};
const promise = new Promise((resolve) => {
const p:TestPromise = {};
p.promise = new Promise<void>((resolve) => {
p.resolve = resolve;
});
p.promise = promise;
testPromises.push(p);
return p.promise;
};
@ -28,8 +35,8 @@ describe(__filename, function () {
});
it('creates another when one completes', async function () {
const {promise, resolve} = testPromises.shift();
resolve();
const {promise, resolve} = testPromises.shift()!;
resolve!();
await promise;
assert.equal(wantIndex, concurrency + 1);
});
@ -39,7 +46,7 @@ describe(__filename, function () {
// Resolve them in random order to ensure that the resolution order doesn't matter.
const i = Math.floor(Math.random() * Math.floor(testPromises.length));
const {promise, resolve} = testPromises.splice(i, 1)[0];
resolve();
resolve!();
await promise;
}
assert.equal(wantIndex, total);
@ -56,8 +63,8 @@ describe(__filename, function () {
const concurrency = 11;
const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
while (testPromises.length > 0) {
const {promise, resolve} = testPromises.pop();
resolve();
const {promise, resolve} = testPromises.pop()!;
resolve!();
await promise;
}
await timesLimitPromise;

View file

@ -1,20 +1,20 @@
'use strict';
const AuthorManager = require('../../../node/db/AuthorManager');
const assert = require('assert').strict;
import {strict as assert} from "assert";
const common = require('../common');
const db = require('../../../node/db/DB');
describe(__filename, function () {
let setBackup;
let setBackup: Function;
before(async function () {
await common.init();
setBackup = db.set;
db.set = async (...args) => {
db.set = async (...args:any) => {
// delay db.set
await new Promise((resolve) => { setTimeout(() => resolve(), 500); });
await new Promise<void>((resolve) => { setTimeout(() => resolve(), 500); });
return await setBackup.call(db, ...args);
};
});

View file

@ -1,7 +1,7 @@
'use strict';
const assert = require('assert').strict;
const path = require('path');
import {strict as assert} from "assert";
import path from 'path';
const sanitizePathname = require('../../../node/utils/sanitizePathname');
describe(__filename, function () {
@ -20,6 +20,7 @@ describe(__filename, function () {
];
for (const [platform, p] of testCases) {
it(`${platform} ${p}`, async function () {
// @ts-ignore
assert.throws(() => sanitizePathname(p, path[platform]), {message: /absolute path/});
});
}
@ -40,6 +41,7 @@ describe(__filename, function () {
];
for (const [platform, p] of testCases) {
it(`${platform} ${p}`, async function () {
// @ts-ignore
assert.throws(() => sanitizePathname(p, path[platform]), {message: /travers/});
});
}
@ -85,6 +87,7 @@ describe(__filename, function () {
for (const [platform, p, tcWant] of testCases) {
const want = tcWant == null ? p : tcWant;
it(`${platform} ${p || '<empty string>'} -> ${want}`, async function () {
// @ts-ignore
assert.equal(sanitizePathname(p, path[platform]), want);
});
}

View file

@ -2,12 +2,12 @@
const assert = require('assert').strict;
const {parseSettings} = require('../../../node/utils/Settings').exportedForTestingOnly;
const path = require('path');
const process = require('process');
import path from 'path';
import process from 'process';
describe(__filename, function () {
describe('parseSettings', function () {
let settings;
let settings:any;
const envVarSubstTestCases = [
{name: 'true', val: 'true', var: 'SET_VAR_TRUE', want: true},
{name: 'false', val: 'false', var: 'SET_VAR_FALSE', want: false},

View file

@ -1,5 +1,7 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
const assert = require('assert').strict;
const common = require('../common');
const padManager = require('../../../node/db/PadManager');
@ -10,9 +12,9 @@ const socketIoRouter = require('../../../node/handler/SocketIORouter');
describe(__filename, function () {
this.timeout(30000);
let agent;
let authorize;
const backups = {};
let agent: any;
let authorize:Function;
const backups:MapArrayType<any> = {};
const cleanUpPads = async () => {
const padIds = ['pad', 'other-pad', 'päd'];
await Promise.all(padIds.map(async (padId) => {
@ -22,7 +24,7 @@ describe(__filename, function () {
}
}));
};
let socket;
let socket:any;
before(async function () { agent = await common.init(); });
beforeEach(async function () {
@ -44,7 +46,7 @@ describe(__filename, function () {
};
assert(socket == null);
authorize = () => true;
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => cb([authorize(req)])}];
plugins.hooks.authorize = [{hook_fn: (hookName: string, {req}:any, cb:Function) => cb([authorize(req)])}];
await cleanUpPads();
});
afterEach(async function () {
@ -84,7 +86,7 @@ describe(__filename, function () {
for (const authn of [false, true]) {
const desc = authn ? 'authn user' : '!authn anonymous';
it(`${desc} read-only /p/pad -> 200, ok`, async function () {
const get = (ep) => {
const get = (ep: string) => {
let res = agent.get(ep);
if (authn) res = res.auth('user', 'user-password');
return res.expect(200);
@ -163,7 +165,9 @@ describe(__filename, function () {
});
it('authorization bypass attempt -> error', async function () {
// Only allowed to access /p/pad.
authorize = (req) => req.path === '/p/pad';
authorize = (req:{
path: string,
}) => req.path === '/p/pad';
settings.requireAuthentication = true;
settings.requireAuthorization = true;
// First authenticate and establish a session.
@ -321,45 +325,46 @@ describe(__filename, function () {
describe('SocketIORouter.js', function () {
const Module = class {
setSocketIO(io) {}
handleConnect(socket) {}
handleDisconnect(socket) {}
handleMessage(socket, message) {}
setSocketIO(io:any) {}
handleConnect(socket:any) {}
handleDisconnect(socket:any) {}
handleMessage(socket:any, message:string) {}
};
afterEach(async function () {
socketIoRouter.deleteComponent(this.test.fullTitle());
socketIoRouter.deleteComponent(`${this.test.fullTitle()} #2`);
socketIoRouter.deleteComponent(this.test!.fullTitle());
socketIoRouter.deleteComponent(`${this.test!.fullTitle()} #2`);
});
it('setSocketIO', async function () {
let ioServer;
socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
setSocketIO(io) { ioServer = io; }
socketIoRouter.addComponent(this.test!.fullTitle(), new class extends Module {
setSocketIO(io:any) { ioServer = io; }
}());
assert(ioServer != null);
});
it('handleConnect', async function () {
let serverSocket;
socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
handleConnect(socket) { serverSocket = socket; }
socketIoRouter.addComponent(this.test!.fullTitle(), new class extends Module {
handleConnect(socket:any) { serverSocket = socket; }
}());
socket = await common.connect();
assert(serverSocket != null);
});
it('handleDisconnect', async function () {
let resolveConnected;
let resolveConnected: (value: void | PromiseLike<void>) => void ;
const connected = new Promise((resolve) => resolveConnected = resolve);
let resolveDisconnected;
const disconnected = new Promise((resolve) => resolveDisconnected = resolve);
socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
handleConnect(socket) {
let resolveDisconnected: (value: void | PromiseLike<void>) => void ;
const disconnected = new Promise<void>((resolve) => resolveDisconnected = resolve);
socketIoRouter.addComponent(this.test!.fullTitle(), new class extends Module {
private _socket: any;
handleConnect(socket:any) {
this._socket = socket;
resolveConnected();
}
handleDisconnect(socket) {
handleDisconnect(socket:any) {
assert(socket != null);
// There might be lingering disconnect events from sockets created by other tests.
if (this._socket == null || socket.id !== this._socket.id) return;
@ -375,40 +380,43 @@ describe(__filename, function () {
});
it('handleMessage (success)', async function () {
let serverSocket;
let serverSocket:any;
const want = {
component: this.test.fullTitle(),
component: this.test!.fullTitle(),
foo: {bar: 'asdf'},
};
let rx;
let rx:Function;
const got = new Promise((resolve) => { rx = resolve; });
socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
handleConnect(socket) { serverSocket = socket; }
handleMessage(socket, message) { assert.equal(socket, serverSocket); rx(message); }
socketIoRouter.addComponent(this.test!.fullTitle(), new class extends Module {
handleConnect(socket:any) { serverSocket = socket; }
handleMessage(socket:any, message:string) { assert.equal(socket, serverSocket); rx(message); }
}());
socketIoRouter.addComponent(`${this.test.fullTitle()} #2`, new class extends Module {
handleMessage(socket, message) { assert.fail('wrong handler called'); }
socketIoRouter.addComponent(`${this.test!.fullTitle()} #2`, new class extends Module {
handleMessage(socket:any, message:any) { assert.fail('wrong handler called'); }
}());
socket = await common.connect();
socket.emit('message', want);
assert.deepEqual(await got, want);
});
const tx = async (socket, message = {}) => await new Promise((resolve, reject) => {
const tx = async (socket:any, message = {}) => await new Promise((resolve, reject) => {
const AckErr = class extends Error {
constructor(name, ...args) { super(...args); this.name = name; }
constructor(name: string, ...args:any) { super(...args); this.name = name; }
};
socket.emit('message', message,
(errj, val) => errj != null ? reject(new AckErr(errj.name, errj.message)) : resolve(val));
(errj: {
message: string,
name: string,
}, val: any) => errj != null ? reject(new AckErr(errj.name, errj.message)) : resolve(val));
});
it('handleMessage with ack (success)', async function () {
const want = 'value';
socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
handleMessage(socket, msg) { return want; }
socketIoRouter.addComponent(this.test!.fullTitle(), new class extends Module {
handleMessage(socket:any, msg:any) { return want; }
}());
socket = await common.connect();
const got = await tx(socket, {component: this.test.fullTitle()});
const got = await tx(socket, {component: this.test!.fullTitle()});
assert.equal(got, want);
});
@ -416,11 +424,11 @@ describe(__filename, function () {
const InjectedError = class extends Error {
constructor() { super('injected test error'); this.name = 'InjectedError'; }
};
socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
handleMessage(socket, msg) { throw new InjectedError(); }
socketIoRouter.addComponent(this.test!.fullTitle(), new class extends Module {
handleMessage(socket:any, msg:any) { throw new InjectedError(); }
}());
socket = await common.connect();
await assert.rejects(tx(socket, {component: this.test.fullTitle()}), new InjectedError());
await assert.rejects(tx(socket, {component: this.test!.fullTitle()}), new InjectedError());
});
});
});

View file

@ -1,12 +1,16 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
const common = require('../common');
const settings = require('../../../node/utils/Settings');
describe(__filename, function () {
this.timeout(30000);
let agent;
const backups = {};
let agent:any;
const backups:MapArrayType<any> = {};
before(async function () { agent = await common.init(); });
beforeEach(async function () {
backups.settings = {};

View file

@ -1,5 +1,9 @@
'use strict';
import {MapArrayType} from "../../../node/types/MapType";
import {Func} from "mocha";
import {SettingsUser} from "../../../node/types/SettingsUser";
const assert = require('assert').strict;
const common = require('../common');
const plugins = require('../../../static/js/pluginfw/plugin_defs');
@ -7,11 +11,11 @@ const settings = require('../../../node/utils/Settings');
describe(__filename, function () {
this.timeout(30000);
let agent;
const backups = {};
let agent:any;
const backups:MapArrayType<any> = {};
const authHookNames = ['preAuthorize', 'authenticate', 'authorize'];
const failHookNames = ['preAuthzFailure', 'authnFailure', 'authzFailure', 'authFailure'];
const makeHook = (hookName, hookFn) => ({
const makeHook = (hookName: string, hookFn:Function) => ({
hook_fn: hookFn,
hook_fn_name: `fake_plugin/${hookName}`,
hook_name: hookName,
@ -19,6 +23,7 @@ describe(__filename, function () {
});
before(async function () { agent = await common.init(); });
beforeEach(async function () {
backups.hooks = {};
for (const hookName of authHookNames.concat(failHookNames)) {
@ -34,8 +39,9 @@ describe(__filename, function () {
settings.users = {
admin: {password: 'admin-password', is_admin: true},
user: {password: 'user-password'},
};
} satisfies SettingsUser;
});
afterEach(async function () {
Object.assign(plugins.hooks, backups.hooks);
Object.assign(settings, backups.settings);
@ -47,56 +53,67 @@ describe(__filename, function () {
settings.requireAuthorization = false;
await agent.get('/').expect(200);
});
it('!authn !authz anonymous /admin/ -> 401', async function () {
settings.requireAuthentication = false;
settings.requireAuthorization = false;
await agent.get('/admin/').expect(401);
});
it('authn !authz anonymous / -> 401', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = false;
await agent.get('/').expect(401);
});
it('authn !authz user / -> 200', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = false;
await agent.get('/').auth('user', 'user-password').expect(200);
});
it('authn !authz user /admin/ -> 403', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = false;
await agent.get('/admin/').auth('user', 'user-password').expect(403);
});
it('authn !authz admin / -> 200', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = false;
await agent.get('/').auth('admin', 'admin-password').expect(200);
});
it('authn !authz admin /admin/ -> 200', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = false;
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
});
it('authn authz anonymous /robots.txt -> 200', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
await agent.get('/robots.txt').expect(200);
});
it('authn authz user / -> 403', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
await agent.get('/').auth('user', 'user-password').expect(403);
});
it('authn authz user /admin/ -> 403', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
await agent.get('/admin/').auth('user', 'user-password').expect(403);
});
it('authn authz admin / -> 200', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
await agent.get('/').auth('admin', 'admin-password').expect(200);
});
it('authn authz admin /admin/ -> 200', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
@ -121,16 +138,21 @@ describe(__filename, function () {
});
describe('webaccess: preAuthorize, authenticate, and authorize hooks', function () {
let callOrder;
let callOrder:string[];
const Handler = class {
constructor(hookName, suffix) {
private called: boolean;
private readonly hookName: string;
private readonly innerHandle: Function;
private readonly id: string;
private readonly checkContext: Function;
constructor(hookName:string, suffix: string) {
this.called = false;
this.hookName = hookName;
this.innerHandle = () => [];
this.id = hookName + suffix;
this.checkContext = () => {};
}
handle(hookName, context, cb) {
handle(hookName: string, context: any, cb:Function) {
assert.equal(hookName, this.hookName);
assert(context != null);
assert(context.req != null);
@ -143,7 +165,7 @@ describe(__filename, function () {
return cb(this.innerHandle(context));
}
};
const handlers = {};
const handlers:MapArrayType<any> = {};
beforeEach(async function () {
callOrder = [];
@ -170,6 +192,7 @@ describe(__filename, function () {
// Note: The preAuthorize hook always runs even if requireAuthorization is false.
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
});
it('bypasses authenticate and authorize hooks when true is returned', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
@ -177,6 +200,7 @@ describe(__filename, function () {
await agent.get('/').expect(200);
assert.deepEqual(callOrder, ['preAuthorize_0']);
});
it('bypasses authenticate and authorize hooks when false is returned', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
@ -184,19 +208,24 @@ describe(__filename, function () {
await agent.get('/').expect(403);
assert.deepEqual(callOrder, ['preAuthorize_0']);
});
it('bypasses authenticate and authorize hooks when next is called', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
handlers.preAuthorize[0].innerHandle = ({next}) => next();
handlers.preAuthorize[0].innerHandle = ({next}:{
next: Function
}) => next();
await agent.get('/').expect(200);
assert.deepEqual(callOrder, ['preAuthorize_0']);
});
it('static content (expressPreSession) bypasses all auth checks', async function () {
settings.requireAuthentication = true;
settings.requireAuthorization = true;
await agent.get('/static/robots.txt').expect(200);
assert.deepEqual(callOrder, []);
});
it('cannot grant access to /admin', async function () {
handlers.preAuthorize[0].innerHandle = () => [true];
await agent.get('/admin/').expect(401);
@ -210,15 +239,17 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('can deny access to /admin', async function () {
handlers.preAuthorize[0].innerHandle = () => [false];
await agent.get('/admin/').auth('admin', 'admin-password').expect(403);
assert.deepEqual(callOrder, ['preAuthorize_0']);
});
it('runs preAuthzFailure hook when access is denied', async function () {
handlers.preAuthorize[0].innerHandle = () => [false];
let called = false;
plugins.hooks.preAuthzFailure = [makeHook('preAuthzFailure', (hookName, {req, res}, cb) => {
plugins.hooks.preAuthzFailure = [makeHook('preAuthzFailure', (hookName: string, {req, res}:any, cb:Function) => {
assert.equal(hookName, 'preAuthzFailure');
assert(req != null);
assert(res != null);
@ -230,6 +261,7 @@ describe(__filename, function () {
await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected');
assert(called);
});
it('returns 500 if an exception is thrown', async function () {
handlers.preAuthorize[0].innerHandle = () => { throw new Error('exception test'); };
await agent.get('/').expect(500);
@ -247,6 +279,7 @@ describe(__filename, function () {
await agent.get('/').expect(200);
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
});
it('is called if !requireAuthentication and /admin/*', async function () {
settings.requireAuthentication = false;
await agent.get('/admin/').expect(401);
@ -255,6 +288,7 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('defers if empty list returned', async function () {
await agent.get('/').expect(401);
assert.deepEqual(callOrder, ['preAuthorize_0',
@ -262,18 +296,21 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('does not defer if return [true], 200', async function () {
handlers.authenticate[0].innerHandle = ({req}) => { req.session.user = {}; return [true]; };
handlers.authenticate[0].innerHandle = ({req}:any) => { req.session.user = {}; return [true]; };
await agent.get('/').expect(200);
// Note: authenticate_1 was not called because authenticate_0 handled it.
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
});
it('does not defer if return [false], 401', async function () {
handlers.authenticate[0].innerHandle = () => [false];
await agent.get('/').expect(401);
// Note: authenticate_1 was not called because authenticate_0 handled it.
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
});
it('falls back to HTTP basic auth', async function () {
await agent.get('/').auth('user', 'user-password').expect(200);
assert.deepEqual(callOrder, ['preAuthorize_0',
@ -281,8 +318,11 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('passes settings.users in context', async function () {
handlers.authenticate[0].checkContext = ({users}) => {
handlers.authenticate[0].checkContext = ({users}:{
users: SettingsUser
}) => {
assert.equal(users, settings.users);
};
await agent.get('/').expect(401);
@ -291,8 +331,13 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('passes user, password in context if provided', async function () {
handlers.authenticate[0].checkContext = ({username, password}) => {
handlers.authenticate[0].checkContext = ({username, password}:{
username: string,
password: string
}) => {
assert.equal(username, 'user');
assert.equal(password, 'user-password');
};
@ -302,8 +347,12 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('does not pass user, password in context if not provided', async function () {
handlers.authenticate[0].checkContext = ({username, password}) => {
handlers.authenticate[0].checkContext = ({username, password}:{
username: string,
password: string
}) => {
assert(username == null);
assert(password == null);
};
@ -313,11 +362,13 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('errors if req.session.user is not created', async function () {
handlers.authenticate[0].innerHandle = () => [true];
await agent.get('/').expect(500);
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
});
it('returns 500 if an exception is thrown', async function () {
handlers.authenticate[0].innerHandle = () => { throw new Error('exception test'); };
await agent.get('/').expect(500);
@ -339,6 +390,7 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('is not called if !requireAuthorization (/admin)', async function () {
settings.requireAuthorization = false;
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
@ -347,6 +399,7 @@ describe(__filename, function () {
'authenticate_0',
'authenticate_1']);
});
it('defers if empty list returned', async function () {
await agent.get('/').auth('user', 'user-password').expect(403);
assert.deepEqual(callOrder, ['preAuthorize_0',
@ -356,6 +409,7 @@ describe(__filename, function () {
'authorize_0',
'authorize_1']);
});
it('does not defer if return [true], 200', async function () {
handlers.authorize[0].innerHandle = () => [true];
await agent.get('/').auth('user', 'user-password').expect(200);
@ -366,6 +420,7 @@ describe(__filename, function () {
'authenticate_1',
'authorize_0']);
});
it('does not defer if return [false], 403', async function () {
handlers.authorize[0].innerHandle = () => [false];
await agent.get('/').auth('user', 'user-password').expect(403);
@ -376,8 +431,11 @@ describe(__filename, function () {
'authenticate_1',
'authorize_0']);
});
it('passes req.path in context', async function () {
handlers.authorize[0].checkContext = ({resource}) => {
handlers.authorize[0].checkContext = ({resource}:{
resource: string
}) => {
assert.equal(resource, '/');
};
await agent.get('/').auth('user', 'user-password').expect(403);
@ -388,6 +446,7 @@ describe(__filename, function () {
'authorize_0',
'authorize_1']);
});
it('returns 500 if an exception is thrown', async function () {
handlers.authorize[0].innerHandle = () => { throw new Error('exception test'); };
await agent.get('/').auth('user', 'user-password').expect(500);
@ -402,12 +461,15 @@ describe(__filename, function () {
describe('webaccess: authnFailure, authzFailure, authFailure hooks', function () {
const Handler = class {
constructor(hookName) {
private hookName: string;
private shouldHandle: boolean;
private called: boolean;
constructor(hookName: string) {
this.hookName = hookName;
this.shouldHandle = false;
this.called = false;
}
handle(hookName, context, cb) {
handle(hookName: string, context:any, cb: Function) {
assert.equal(hookName, this.hookName);
assert(context != null);
assert(context.req != null);
@ -421,7 +483,7 @@ describe(__filename, function () {
return cb([]);
}
};
const handlers = {};
const handlers:MapArrayType<any> = {};
beforeEach(async function () {
failHookNames.forEach((hookName) => {
@ -440,6 +502,7 @@ describe(__filename, function () {
assert(!handlers.authzFailure.called);
assert(handlers.authFailure.called);
});
it('authn fail, authnFailure handles', async function () {
handlers.authnFailure.shouldHandle = true;
await agent.get('/').expect(200, 'authnFailure');
@ -447,6 +510,7 @@ describe(__filename, function () {
assert(!handlers.authzFailure.called);
assert(!handlers.authFailure.called);
});
it('authn fail, authFailure handles', async function () {
handlers.authFailure.shouldHandle = true;
await agent.get('/').expect(200, 'authFailure');
@ -454,6 +518,7 @@ describe(__filename, function () {
assert(!handlers.authzFailure.called);
assert(handlers.authFailure.called);
});
it('authnFailure trumps authFailure', async function () {
handlers.authnFailure.shouldHandle = true;
handlers.authFailure.shouldHandle = true;
@ -469,6 +534,7 @@ describe(__filename, function () {
assert(handlers.authzFailure.called);
assert(handlers.authFailure.called);
});
it('authz fail, authzFailure handles', async function () {
handlers.authzFailure.shouldHandle = true;
await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
@ -476,6 +542,7 @@ describe(__filename, function () {
assert(handlers.authzFailure.called);
assert(!handlers.authFailure.called);
});
it('authz fail, authFailure handles', async function () {
handlers.authFailure.shouldHandle = true;
await agent.get('/').auth('user', 'user-password').expect(200, 'authFailure');
@ -483,6 +550,7 @@ describe(__filename, function () {
assert(handlers.authzFailure.called);
assert(handlers.authFailure.called);
});
it('authzFailure trumps authFailure', async function () {
handlers.authzFailure.shouldHandle = true;
handlers.authFailure.shouldHandle = true;