From ab1244f31f11eb00e501c762ec6324f35d747189 Mon Sep 17 00:00:00 2001 From: jmulcahey-twilio <74629676+jmulcahey-twilio@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:45:11 -0700 Subject: [PATCH] test: add template verification tests (#205) --- .../{test => tests}/broadcast-sms.test.js | 0 airtable/{test => tests}/save-sms.test.js | 0 .../{test => tests}/blocklist-call.test.js | 0 .../{test => tests}/forward-call.test.js | 0 package.json | 10 +- test/all-templates.test.js | 134 ++++++++++++++++-- 6 files changed, 122 insertions(+), 22 deletions(-) rename airtable/{test => tests}/broadcast-sms.test.js (100%) rename airtable/{test => tests}/save-sms.test.js (100%) rename blocklist-call/{test => tests}/blocklist-call.test.js (100%) rename forward-call/{test => tests}/forward-call.test.js (100%) diff --git a/airtable/test/broadcast-sms.test.js b/airtable/tests/broadcast-sms.test.js similarity index 100% rename from airtable/test/broadcast-sms.test.js rename to airtable/tests/broadcast-sms.test.js diff --git a/airtable/test/save-sms.test.js b/airtable/tests/save-sms.test.js similarity index 100% rename from airtable/test/save-sms.test.js rename to airtable/tests/save-sms.test.js diff --git a/blocklist-call/test/blocklist-call.test.js b/blocklist-call/tests/blocklist-call.test.js similarity index 100% rename from blocklist-call/test/blocklist-call.test.js rename to blocklist-call/tests/blocklist-call.test.js diff --git a/forward-call/test/forward-call.test.js b/forward-call/tests/forward-call.test.js similarity index 100% rename from forward-call/test/forward-call.test.js rename to forward-call/tests/forward-call.test.js diff --git a/package.json b/package.json index d8f35452..3827a690 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,8 @@ "jest": { "testEnvironment": "node", "collectCoverage": true, - "coveragePathIgnorePatterns": [ - "/node_modules/", - "/test/" - ], - "testPathIgnorePatterns": [ - "/_helpers/", - "/node_modules/" - ] + "coveragePathIgnorePatterns": ["/node_modules/", "/test/"], + "testPathIgnorePatterns": ["/_helpers/", "/node_modules/"] }, "dependencies": { "got": "^6.7.1" diff --git a/test/all-templates.test.js b/test/all-templates.test.js index e5623cb1..f13ff5d6 100644 --- a/test/all-templates.test.js +++ b/test/all-templates.test.js @@ -1,32 +1,138 @@ 'use strict'; -// This file contains Jest tests that run against every function template in -// the repo. +// These tests verify that some important guarantees hold for all the functions +// in the repository. const path = require('path'); const fs = require('fs'); +const { parser } = require('configure-env'); -const excludedPaths = ['node_modules', 'test', 'coverage', 'docs']; +// skipList is a list of function templates that don't pass verification +// for now, but will in the long term +const skipList = ['conversations', 'funlet-call-me', 'funlet-echo', + 'funlet-find-me', 'funlet-forward', 'funlet-simple-menu', + 'funlet-simple-message', 'funlet-simulring', + 'funlet-whisper', 'vaccine-standby']; +const excludedPaths = ['node_modules', 'test', 'coverage', 'docs', 'blank'] + skipList; const projectRoot = path.resolve(__dirname, '..'); // Assemble a list of template directories here, since templates.json // may not have all of them: const templates = fs .readdirSync(projectRoot, { withFileTypes: true }) .filter( - (file) => - file.isDirectory() && - !file.name.startsWith('.') && - !file.name.startsWith('_') && - !excludedPaths.includes(file.name) + (file) => + file.isDirectory() && + !file.name.startsWith('.') && + !file.name.startsWith('_') && + !excludedPaths.includes(file.name) ) .map((dir) => dir.name); -describe.each(templates)('the "%s" function template', (template) => { - it('should have a .env file', (done) => { - const envFile = path.join(projectRoot, template, '.env'); - fs.access(envFile, fs.constants.F_OK, (err) => { - expect(err).toBeFalsy(); - done(); +describe('CI template verification', () => { + describe.each(templates)('the "%s" function template', (template) => { + it('should have a populated functions directory', (done) => { + const functionsDir = path.join(projectRoot, template, + 'functions'); + + fs.readdir(functionsDir, (err, files) => { + expect(err).toBeFalsy(); + expect(files.length).toBeGreaterThan(0); + done(); + }); + }); + + it('should have a populated tests directory', (done) => { + const functionsDir = path.join(projectRoot, template, 'tests'); + + fs.readdir(functionsDir, (err, files) => { + expect(err).toBeFalsy(); + expect(files.length).toBeGreaterThan(0); + done(); + }); + }); + + describe('its assets/index.html file', () => { + const indexFile = path.join(projectRoot, template, + 'assets', 'index.html'); + + it('should exist', (done) => { + fs.access(indexFile, fs.constants.F_OK, (err) => { + expect(err).toBeFalsy(); + done(); + }); + }); + + it('should contain (for new-style index pages)', (done) => { + fs.readFile(indexFile, (err, data) => { + expect(err).toBeFalsy(); + if(data.includes('ce-paste-theme.css')) { + expect(data.includes('')); + } + done(); + }); + }); + }); + + describe('its .env file', () => { + const envFile = path.join(projectRoot, template, '.env'); + + it('should exist', (done) => { + fs.access(envFile, fs.constants.F_OK, (err) => { + expect(err).toBeFalsy(); + done(); + }); + }); + + it('should be parseable', async () => { + const result = await parser.parseFile(envFile); + + expect(result).toBeTruthy(); + }); + + it('should have a description for each variable', async () => { + const result = await parser.parseFile(envFile); + + result.variables.forEach((v) => { + if(!v.description) { + throw new Error(`${v.key} is missing a description`); + } + }); + }); + + it('should have non-configurable TWILIO_*_WEBHOOK_URL variables, if present', async () => { + const result = await parser.parseFile(envFile); + const varRegex = /^TWILIO_.*_WEBHOOK_URL$/; + + result.variables + .filter((v) => varRegex.test(v.key)) + .forEach((v) => { + if(v.configurable) { + throw new Error(`${v.key} should not be configurable`); + } + }); + }); + }); + + describe('its package.json file', () => { + const packageJson = path.join(projectRoot, template, 'package.json'); + + it('should exist', (done) => { + fs.access(packageJson, fs.constants.F_OK, (err) => { + expect(err).toBeFalsy(); + done(); + }); + }); + + it('should be parseable as a Javascript object', (done) => { + fs.readFile(packageJson, (err, contents) => { + expect(err).toBeFalsy(); + const data = JSON.parse(contents); + + expect(data).toBeTruthy(); + expect(data.constructor).toBe(Object); + done(); + }); + }); }); }); });