From ea2ebf99e6ec671e3209949512ed256cace9dc58 Mon Sep 17 00:00:00 2001 From: Paul Puey Date: Sat, 18 Nov 2023 23:10:54 -0800 Subject: [PATCH] Add loginServer tests --- src/plugins/loginServer.ts | 181 ++++++++++++++++++++++++++++++++++--- src/util/testing.ts | 51 +++++++++++ 2 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 src/util/testing.ts diff --git a/src/plugins/loginServer.ts b/src/plugins/loginServer.ts index 7fc2495..f27fae7 100644 --- a/src/plugins/loginServer.ts +++ b/src/plugins/loginServer.ts @@ -1,27 +1,84 @@ -import { asArray, asMaybe, asObject, asString } from 'cleaners' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import { + asArray, + asDate, + asMaybe, + asNumber, + asObject, + asString, + Cleaner +} from 'cleaners' import { syncedDocument } from 'edge-server-tools' +import { RequestInit } from 'node-fetch' import { MonitorPlugin } from '../../types' -import { logObject, PluginInitializer, PluginProcessor } from '../util/utils' +import { describe, it } from '../util/testing' +import { cleanFetch, PluginInitializer, PluginProcessor } from '../util/utils' +chai.use(chaiAsPromised) + +const { assert } = chai const pluginId = 'loginServer' -const runFrequencyMs = 60000 +const runFrequencyMs = 1000 * 60 * 5 // 5 mins +const asLoginServerCluster = asObject({ + servers: asMaybe(asArray(asString), () => ['https://login.edge.app']), + edgeApiKey: asMaybe(asString, 'dummyApiKey'), + passwordUserId: asMaybe(asString, 'dummyKey'), + passwordAuth: asMaybe(asString, 'dummyPasswordAuth'), + pin2Id: asMaybe(asString, 'dummyPin2Id'), + pin2Auth: asMaybe(asString, 'dummyPin2Auth'), + userIdExists: asMaybe(asString, 'xxxx'), + userIdAvailable: asMaybe(asString, 'xxxx') +}) const asLoginServerData = asObject({ doc: asMaybe( asObject({ - servers: asMaybe(asArray(asString), () => []), - loginKey: asMaybe(asString, 'dummyKey'), - apiKey: asMaybe(asString, 'dummyApiKey') + clusters: asMaybe(asArray(asLoginServerCluster), () => [ + asLoginServerCluster({}) + ]) }), () => ({ - servers: [], - loginKey: 'dummyKey', - apiKey: 'dummyApiKey' + clusters: [asLoginServerCluster({})] }) ) }) +const asLoginResult = asObject({ + results: asObject({ + appId: asString, + created: asDate, + loginId: asString, + syncToken: asString, + passwordAuthBox: asObject({ + encryptionType: asNumber, + data_base64: asString, + iv_hex: asString + }), + passwordAuthSnrp: asObject({ + salt_hex: asString, + n: asNumber, + r: asNumber, + p: asNumber + }) + }) +}) + +const asUserIdCheck = asObject({ + results: asObject({ + loginId: asString, + passwordAuthSnrp: asObject({ + salt_hex: asString, + n: asNumber, + r: asNumber, + p: asNumber + }) + }) +}) + +type LoginServerCluster = ReturnType + const syncedDoc = syncedDocument(pluginId, asLoginServerData) const initializePlugin: PluginInitializer = async ( @@ -34,8 +91,110 @@ const pluginProcessor: PluginProcessor = async ( connection ): Promise => { if (syncedDoc == null) return - logObject(serverConfig) - logObject(syncedDoc) + const { clusters } = syncedDoc.doc.doc + await describe('loginServers', async () => { + for (const cluster of clusters) { + const { servers } = cluster + for (const server of servers) { + await it('testPasswordLogin', async () => + await testPasswordLogin(server, cluster)) + await it('testPin2Login', async () => + await testPin2Login(server, cluster)) + await it('testUserIdExists', async () => + await testUserIdExists(server, cluster)) + await it('testUserIdAvailable', async () => + await testUserIdAvailable(server, cluster)) + } + } + }) +} + +const testPasswordLogin = async ( + server: string, + cluster: LoginServerCluster +): Promise => { + const { passwordUserId, passwordAuth } = cluster + await fetchApiLogin( + server, + cluster, + { + userId: passwordUserId, + passwordAuth + }, + asLoginResult + ) +} + +const testPin2Login = async ( + server: string, + cluster: LoginServerCluster +): Promise => { + const { pin2Id, pin2Auth } = cluster + await fetchApiLogin( + server, + cluster, + { + pin2Id, + pin2Auth + }, + asLoginResult + ) +} + +const testUserIdExists = async ( + server: string, + cluster: LoginServerCluster +): Promise => { + const { userIdExists } = cluster + await fetchApiLogin( + server, + cluster, + { + userId: userIdExists + }, + asUserIdCheck + ) +} + +const testUserIdAvailable = async ( + server: string, + cluster: LoginServerCluster +): Promise => { + const { userIdAvailable } = cluster + + // Available usernames will return a 400 on fetch and would + // throw on fetchApiLogin, hence assert that the promise rejects + await assert.isRejected( + fetchApiLogin( + server, + cluster, + { + userId: userIdAvailable + }, + asUserIdCheck + ) + ) +} + +const fetchApiLogin = async ( + server: string, + cluster: LoginServerCluster, + body: any, + cleaner: Cleaner +): Promise => { + const { edgeApiKey } = cluster + + const url = `${server}/api/v2/login` + const options: RequestInit = { + headers: { + Authorization: `Token ${edgeApiKey}`, + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(body) + } + const result = await cleanFetch(url, options, cleaner) + return result } export const loginServerPlugin: MonitorPlugin = { diff --git a/src/util/testing.ts b/src/util/testing.ts new file mode 100644 index 0000000..fd5b3fc --- /dev/null +++ b/src/util/testing.ts @@ -0,0 +1,51 @@ +import { slackStatus } from './services/slackAlert' +import { datelog } from './utils' + +interface TestResult { + description: string + pass: boolean + error?: string +} + +let results: TestResult[] = [] + +export const describe = async ( + description: string, + func: () => Promise +): Promise => { + datelog(`*** ${description} START ***`) + results = [] + await func() + datelog(`*** ${description} END *****`) + const failures = results.filter(r => !r.pass) + if (failures.length > 0) { + const messages = failures.map( + f => `[FAILED] ${f.description}: ${f.error ?? ''}` + ) + const message = `FAILED Test group ${description}\n` + messages.join('\n') + console.log(`Messaging Errors to Slack\n${message}`) + slackStatus(message) + } +} + +export const it = async ( + description: string, + func: () => Promise +): Promise => { + try { + await func() + results.push({ + description, + pass: true + }) + datelog(`\x1b[32m✔ [PASSED]\x1b[0m ${description}`) + } catch (e) { + const error = String(e) + results.push({ + description, + pass: false, + error + }) + datelog(`\x1b[31m✘ [FAILED]\x1b[0m ${description} ${error}`) + } +}