Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate alert push notification tests #1735

Merged
merged 12 commits into from
Jan 3, 2020
15 changes: 12 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const JEST_PROJECT = process.env.JEST_PROJECT
ptbrowne marked this conversation as resolved.
Show resolved Hide resolved

module.exports = {
moduleFileExtensions: ['js', 'jsx', 'json', 'styl'],
moduleDirectories: ['node_modules', '<rootDir>/src', '<rootDir>'],
Expand All @@ -10,7 +12,15 @@ module.exports = {
'^cozy-client$': 'cozy-client/dist/index'
},
snapshotSerializers: ['enzyme-to-json/serializer'],
testPathIgnorePatterns: ['node_modules', 'src/targets/mobile/'],
testPathIgnorePatterns: [
'node_modules',
'src/targets/mobile/',
JEST_PROJECT === 'e2e' ? null : '.*\\.e2e\\.spec\\.js'
].filter(Boolean),
testMatch: [
JEST_PROJECT === 'e2e' ? null : '**/?(*.)(spec).js?(x)',
JEST_PROJECT === 'e2e' ? '**/?(*.)(e2e.spec).js?(x)' : null
].filter(Boolean),
transform: {
'^.+\\.jsx?$': 'babel-jest',
'\\.css$': '<rootDir>/test/readFileESM.js',
Expand All @@ -28,6 +38,5 @@ module.exports = {
__SENTRY_TOKEN__: 'token',
cozy: {}
},
setupFiles: ['jest-localstorage-mock', './test/jest.setup.js'],
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)(spec).js?(x)']
setupFiles: ['jest-localstorage-mock', './test/jest.setup.js']
}
163 changes: 75 additions & 88 deletions test/e2e/alerts.js → test/e2e/alerts.e2e.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/* eslint-disable no-console */

import { spawnSync } from 'child_process'
import { ArgumentParser } from 'argparse'
import isMatch from 'lodash/isMatch'
import pick from 'lodash/pick'
import pickBy from 'lodash/pickBy'
import omit from 'lodash/omit'
Expand All @@ -16,13 +14,15 @@ import {
BILLS_DOCTYPE
} from '../../src/doctypes'
import { importData } from './dataUtils'
import { question } from './interactionUtils'
import Mailhog from 'mailhog'
import MockServer from './mock-server'
import scenarios from './scenarios'
import fs from 'fs'

const SOFTWARE_ID = 'banks.alerts-e2e'

jest.setTimeout(10 * 1000)

const revokeOtherOAuthClientsForSoftwareId = async (client, softwareID) => {
const { data: clients } = await client.stackClient.fetchJSON(
'GET',
Expand All @@ -42,15 +42,6 @@ const revokeOtherOAuthClientsForSoftwareId = async (client, softwareID) => {
}
}

const parseArgs = () => {
const parser = new ArgumentParser()
parser.addArgument('--url', { defaultValue: 'http://cozy.tools:8080' })
parser.addArgument(['-v', '--verbose'], { action: 'storeTrue' })
parser.addArgument(['--push'], { action: 'storeTrue' })
parser.addArgument('scenario')
return parser.parseArgs()
}

const decodeEmail = (mailhog, attrs) =>
attrs
? {
Expand Down Expand Up @@ -86,53 +77,35 @@ const runService = async options => {
}
}

const expectMatch = (expected, received) => {
if (expected === null) {
if (!received) {
return true
} else {
console.error('Error: expected null but received something')
console.log('Received', received)
return false
}
}
const isMatching = isMatch(received, expected)
if (isMatching) {
return true
} else {
console.error('Error:', received, 'does not match expected', expected)
return false
}
}

const checkEmailForScenario = async (mailhog, scenario) => {
const latestMessages = (await mailhog.messages(0, 1)).items
const email = decodeEmail(
mailhog,
latestMessages.length > 0 ? pick(latestMessages[0], ['subject']) : null
)
return expectMatch(email, scenario.expected.email)
if (scenario.expected.email) {
expect(email).toMatchObject(scenario.expected.email)
} else {
expect(email).toBeFalsy()
}
}

const checkPushForScenario = async (pushServer, scenario) => {
let lastReq
try {
await pushServer.waitForRequest({ timeout: 5000 })
await pushServer.waitForRequest({ timeout: 1000 })
lastReq = pushServer.getLastRequest()
} catch (e) {
// eslint-disable-line empty-catch
}
if (scenario.expected.notification !== undefined) {
return expectMatch(scenario.expected.notification, lastReq && lastReq.body)
if (scenario.expected.notification) {
expect(lastReq.body).toMatchObject(scenario.expected.notification)
} else {
const answer = await question('Is scenario OK (y|n) ? ')
return answer === 'y'
expect(lastReq).toBeFalsy()
}
}

const runScenario = async (client, scenarioId, options) => {
console.log('Running scenario', scenarioId)
const scenario = scenarios[scenarioId]
const runScenario = async (client, scenario, options) => {
await importData(client, scenario.data)

if (options.mailhog) {
Expand All @@ -142,14 +115,7 @@ const runScenario = async (client, scenarioId, options) => {
options.pushServer.clearRequests()
}

console.log('Running service...')
try {
await runService(options)
} catch (e) {
return false
}

console.log('Description: ', scenario.description)
await runService(options)

if (options.mailhog) {
const emailMatch = await checkEmailForScenario(options.mailhog, scenario)
Expand All @@ -169,21 +135,26 @@ const cleanupDatabase = async client => {
BILLS_DOCTYPE
]) {
const col = client.collection(doctype)
console.log(`Fetching docs ${doctype}`)
const { data: docs } = await col.getAll()
if (docs.length > 0) {
console.log(`Cleaning ${docs.length} ${doctype} documents`)
// The omit for _type can be removed when the following PR is resolved
// https://github.com/cozy/cozy-client/pull/597
await col.destroyAll(docs.map(doc => omit(doc, '_type')))
}
}
}

const main = async () => {
const args = parseArgs()
const setupClient = async options => {
try {
fs.unlinkSync(
'/tmp/cozy-client-oauth-cozy-tools:8080-banks.alerts-e2e.json'
)
} catch (e) {
// eslint-disable-next-line empty-block
}

const client = await createClientInteractive({
uri: args.url,
uri: options.url,
scope: [
'io.cozy.oauth.clients:ALL',
SETTINGS_DOCTYPE,
Expand All @@ -198,55 +169,71 @@ const main = async () => {
})

await revokeOtherOAuthClientsForSoftwareId(client, SOFTWARE_ID)

if (args.push) {
if (options.push) {
const clientInfos = client.stackClient.oauthOptions
await client.stackClient.updateInformation({
...clientInfos,
notificationPlatform: 'android',
notificationDeviceToken: 'fake-token'
})
}
return client
}

const allScenarioIds = Object.keys(scenarios)
const scenarioIds =
args.scenario === 'all'
? allScenarioIds
: allScenarioIds.filter(x => x.startsWith(args.scenario))
describe('alert emails/notifications', () => {
let client
let pushServer
let mailhog
let options = {
url: process.env.COZY_URL || 'http://cozy.tools:8080',
verbose: false
}

const mailhog = args.push ? null : Mailhog({ host: 'localhost' })
const pushServer = args.push ? new MockServer() : null
if (pushServer) {
beforeAll(async () => {
pushServer = new MockServer()
await pushServer.listen()
}
mailhog = Mailhog({ host: 'localhost' })
})

afterAll(async () => {
await pushServer.close()
})

const answers = {}
for (const scenarioId of scenarioIds) {
beforeEach(async () => {
await cleanupDatabase(client)
const res = await runScenario(client, scenarioId, {
showOutput: args.verbose,
mailhog,
pushServer: pushServer
})

describe('push', () => {
beforeAll(async () => {
client = await setupClient({ url: options.url })
})
answers[scenarioId] = res
}

for (const [scenarioId, answer] of Object.entries(answers)) {
console.log(
answer ? '✅' : '❌',
scenarioId,
`(${scenarios[scenarioId].description})`
)
}
for (const scenario of Object.values(scenarios)) {
describe(scenario.description, () => {
it('should work', async () => {
ptbrowne marked this conversation as resolved.
Show resolved Hide resolved
await runScenario(client, scenario, {
showOutput: options.verbose,
mailhog
})
})
})
}
})

await pushServer.close()
}
describe('email', () => {
beforeAll(async () => {
client = await setupClient({ url: options.url, push: true })
})

main()
.catch(e => {
console.error(e)
process.exit(1)
})
.then(() => {
process.exit(0)
for (const scenario of Object.values(scenarios)) {
describe(scenario.description, () => {
it('should work', async () => {
ptbrowne marked this conversation as resolved.
Show resolved Hide resolved
await runScenario(client, scenario, {
showOutput: options.verbose,
pushServer
})
})
})
}
})
})