diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 6a2dfd6..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,197 +0,0 @@ -version: 2.1 - -restore_npm_cache: &restore_npm_cache - restore_cache: - keys: - - v1-dependencies-{{ checksum "package.json" }} - - v1-dependencies- - -install_npm_packages: &install_npm_packages - run: - name: install dependencies - command: npm install - -save_npm_cache: &save_npm_cache - save_cache: - paths: - - ./node_modules - key: v1-dependencies-{{ checksum "package.json" }} - -fix_file_suffixes_for_cloud_gov: &fix_file_suffixes_for_cloud_gov - run: - name: Delete Config.js and drop sufffix on Config.js.cloudgov - command: | - rm ./src/config.js - mv ./src/config.js.cloudgov ./src/config.js - rm knexfile.js - mv knexfile.js.cloudgov knexfile.js - -install_cf_cli: &install_cf_cli - run: - name: Install CF CLI - command: | - sudo curl -v -L -o cf8-cli-installer_8.7.4_x86-64.deb 'https://packages.cloudfoundry.org/stable?release=debian64&version=8.7.4' - sudo dpkg -i cf8-cli-installer_8.7.4_x86-64.deb - -jobs: - lint_js: - docker: - - image: cimg/node:20.11.0-browsers - steps: - - checkout - - *restore_npm_cache - - *install_npm_packages - - *save_npm_cache - - run: - name: lint javascript - command: npm run lint - test: - docker: - - image: cimg/node:20.11.0-browsers - environment: - POSTGRES_USER: postgres - NODE_ENV: test - - image: circleci/postgres:9.5-alpine - environment: - POSTGRES_DB: analytics-api-test - steps: - - checkout - - *restore_npm_cache - - *install_npm_packages - - *save_npm_cache - - run: - name: unit test javascript - command: npm test - development_env_deploy: - docker: - - image: cimg/node:20.11.0-browsers - steps: - - checkout - - *restore_npm_cache - - *install_npm_packages - - *save_npm_cache - - run: - name: Replace Data URL in manifest.yml file when deploying to develop - command: | - sudo apt-get update - sudo apt-get install gettext - sed -i 's@name: analytics-reporter-api@name: analytics-reporter-api-develop@g' manifest.yml - sed -i 's@- analytics-reporter-database@- analytics-reporter-database-develop@g' manifest.yml - mv manifest.yml manifest.yml.src - envsubst < manifest.yml.src > manifest.yml - - run: - name: Run sed on entrypoint.sh when deploying to develop - command: | - sed -i 's@NEW_RELIC_APP_NAME="analytics-reporter-api"@NEW_RELIC_APP_NAME="analytics-reporter-api-develop"@g' entrypoint.sh - - *fix_file_suffixes_for_cloud_gov - - *install_cf_cli - - run: - name: deploy - command: | - set -e - # Log into cloud.gov - cf api api.fr.cloud.gov - cf login -u $CF_USERNAME_DEV -p $CF_PASSWORD_DEV -o gsa-opp-analytics -s analytics-dev - cat manifest.yml - cf push -f "./manifest.yml" - cf set-env analytics-reporter-api-develop API_DATA_GOV_SECRET "$API_SECRET_LOWER" - cf restage analytics-reporter-api-develop - cf logout - staging_env_deploy: - docker: - - image: cimg/node:20.11.0-browsers - steps: - - checkout - - *restore_npm_cache - - *install_npm_packages - - *save_npm_cache - - run: - name: Replace Data URL in manifest.yml file when deploying to staging - command: | - sudo apt-get update - sudo apt-get install gettext - sed -i 's@name: analytics-reporter-api@name: analytics-reporter-api-staging@g' manifest.yml - sed -i 's@- analytics-reporter-database@- analytics-reporter-database-staging@g' manifest.yml - mv manifest.yml manifest.yml.src - envsubst < manifest.yml.src > manifest.yml - - run: - name: Run sed on entrypoint.sh when deploying to staging - command: | - sed -i 's@NEW_RELIC_APP_NAME="analytics-reporter-api"@NEW_RELIC_APP_NAME="analytics-reporter-api-staging"@g' entrypoint.sh - - *fix_file_suffixes_for_cloud_gov - - *install_cf_cli - - run: - name: deploy - command: | - set -e - # Log into cloud.gov - cf api api.fr.cloud.gov - cf login -u $CF_STAGING_SPACE_DEPLOYER_USERNAME -p $CF_STAGING_SPACE_DEPLOYER_PASSWORD -o gsa-opp-analytics -s analytics-staging - cat manifest.yml - cf push -f "./manifest.yml" - cf set-env analytics-reporter-api-staging API_DATA_GOV_SECRET "$API_SECRET_LOWER" - cf restage analytics-reporter-api-staging - cf logout - production_env_deploy: - docker: - - image: cimg/node:20.11.0-browsers - steps: - - checkout - - *restore_npm_cache - - *install_npm_packages - - *save_npm_cache - - run: - name: Replace Data URL in manifest.yml file when deploying to production - command: | - sudo apt-get update - sudo apt-get install gettext - sed -i 's@name: analytics-reporter-api@name: analytics-reporter-api-production@g' manifest.yml - sed -i 's@- analytics-reporter-database@- analytics-reporter-database-production@g' manifest.yml - mv manifest.yml manifest.yml.src - envsubst < manifest.yml.src > manifest.yml - - run: - name: Run sed on entrypoint.sh when deploying to main - command: | - sed -i 's@NEW_RELIC_APP_NAME="analytics-reporter-api"@NEW_RELIC_APP_NAME="analytics-reporter-api-production"@g' entrypoint.sh - - *fix_file_suffixes_for_cloud_gov - - *install_cf_cli - - run: - name: deploy - command: | - set -e - # Log into cloud.gov - cf api api.fr.cloud.gov - cf login -u $CF_PRODUCTION_SPACE_DEPLOYER_USERNAME -p $CF_PRODUCTION_SPACE_DEPLOYER_PASSWORD -o gsa-opp-analytics -s analytics-prod - cat manifest.yml - cf push -f "./manifest.yml" - cf set-env analytics-reporter-api-production API_DATA_GOV_SECRET "$API_SECRET" - cf restage analytics-reporter-api-production - cf logout - -ci: - jobs: - - lint - - test: - requires: - - lint - - development_env_deploy: - requires: - - test - filters: - branches: - only: - - develop - - staging_env_deploy: - requires: - - test - filters: - branches: - only: - - staging - - production_env_deploy: - requires: - - test - filters: - branches: - only: - - master diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b0e85b7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,126 @@ +on: + push: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Code Checkout + uses: actions/checkout@v4 + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + cache: 'npm' + - name: Create Checksum of package.json + run: echo PACKAGE_CHECKSUM="$(shasum package.json | awk '{ print $1 }')" >> "$GITHUB_ENV" + - name: Restore Cache + uses: actions/cache/restore@v4 + with: + path: ./node_modules + key: v1-dependencies-${{ env.PACKAGE_CHECKSUM }} + - name: Install Dependencies + run: npm install + - name: Save Cache + uses: actions/cache/save@v4 + id: cache + with: + path: ./node_modules + key: v1-dependencies-${{ env.PACKAGE_CHECKSUM }} + - name: lint javascript + run: npm run lint + test: + needs: lint + runs-on: ubuntu-latest + #Spin up postgres as a service, wait till healthy before moving on. Uses latest Postgres Version. + services: + postgres: + image: postgres:latest + env: + POSTGRES_DB: analytics_reporter_test + POSTGRES_USER: analytics + POSTGRES_PASSWORD: 123abc + ports: + - 5432:5432 + options: + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Code Checkout + uses: actions/checkout@v4 + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + cache: 'npm' + - name: Create Checksum of package.json + run: echo PACKAGE_CHECKSUM="$(shasum package.json | awk '{ print $1 }')" >> "$GITHUB_ENV" + - name: Restore Cache + uses: actions/cache/restore@v4 + with: + path: ./node_modules + key: v1-dependencies-${{ env.PACKAGE_CHECKSUM }} + - name: Install Dependencies + run: npm install + - name: Save Cache + uses: actions/cache/save@v4 + id: cache + with: + path: ./node_modules + key: v1-dependencies-${{ env.PACKAGE_CHECKSUM }} + - name: run tests + run: npm test + deploy_dev: + needs: + - lint + - test + if: github.ref == 'refs/heads/develop' + uses: 18F/analytics-reporter-api/.github/workflows/deploy.yml@develop + with: + APP_NAME: ${{ vars.APP_NAME_DEV }} + DB_NAME: ${{ vars.DB_NAME_DEV }} + NEW_RELIC_APP_NAME: + ORGANIZATION_NAME: gsa-opp-analytics + SPACE_NAME: analytics-dev + secrets: + CF_USERNAME: ${{ secrets.CF_USERNAME_DEV }} + CF_PASSWORD: ${{ secrets.CF_PASSWORD_DEV }} + API_DATA_GOV_SECRET: ${{ secrets.API_DATA_GOV_SECRET_DEV }} + NEW_RELIC_LICENSE_KEY: + deploy_stg: + needs: + - lint + - test + if: github.ref == 'refs/heads/staging' + uses: 18F/analytics-reporter-api/.github/workflows/deploy.yml@develop + with: + APP_NAME: ${{ vars.APP_NAME_STG }} + DB_NAME: ${{ vars.DB_NAME_STG }} + NEW_RELIC_APP_NAME: + ORGANIZATION_NAME: gsa-opp-analytics + SPACE_NAME: analytics-staging + secrets: + CF_USERNAME: ${{ secrets.CF_USERNAME_STG }} + CF_PASSWORD: ${{ secrets.CF_PASSWORD_STG }} + API_DATA_GOV_SECRET: ${{ secrets.API_DATA_GOV_SECRET_STG }} + NEW_RELIC_LICENSE_KEY: + deploy_prd: + needs: + - lint + - test + if: github.ref == 'refs/heads/master' + uses: 18F/analytics-reporter-api/.github/workflows/deploy.yml@develop + with: + APP_NAME: ${{ vars.APP_NAME_PRD }} + DB_NAME: ${{ vars.DB_NAME_PRD }} + NEW_RELIC_APP_NAME: ${{ vars.NEW_RELIC_APP_NAME_PRD }} + ORGANIZATION_NAME: gsa-opp-analytics + SPACE_NAME: analytics-production + secrets: + CF_USERNAME: ${{ secrets.CF_USERNAME_PRD }} + CF_PASSWORD: ${{ secrets.CF_PASSWORD_PRD }} + API_DATA_GOV_SECRET: ${{ secrets.API_DATA_GOV_SECRET_PRD }} + NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY_PRD }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..44eabfc --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,79 @@ +on: + workflow_call: + inputs: + APP_NAME: + required: true + type: string + DB_NAME: + required: true + type: string + NEW_RELIC_APP_NAME: + type: string + ORGANIZATION_NAME: + required: true + type: string + SPACE_NAME: + required: true + type: string + secrets: + CF_USERNAME: + required: true + CF_PASSWORD: + required: true + API_DATA_GOV_SECRET: + required: true + NEW_RELIC_LICENSE_KEY: + +jobs: + deploy_api: + runs-on: ubuntu-latest + steps: + - name: Code Checkout + uses: actions/checkout@v4 + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + cache: 'npm' + - name: Create Checksum of package.json + run: echo PACKAGE_CHECKSUM="$(shasum package.json | awk '{ print $1 }')" >> "$GITHUB_ENV" + - name: Restore Cache + uses: actions/cache/restore@v4 + with: + path: ./node_modules + key: v1-dependencies-${{ env.PACKAGE_CHECKSUM }} + - name: Install Dependencies + run: npm install + - name: Save Cache + uses: actions/cache/save@v4 + id: cache + with: + path: ./node_modules + key: v1-dependencies-${{ env.PACKAGE_CHECKSUM }} + - name: Replace env vars in manifest.yml file + run: | + sudo apt-get update + sudo apt-get install gettext + mv manifest.yml manifest.yml.src + envsubst < manifest.yml.src > manifest.yml + - name: Replace config.js and knexfile.js with .cloudgov versions of those files + run: | + rm ./src/config.js + mv ./src/config.js.cloudgov ./src/config.js + rm knexfile.js + mv knexfile.js.cloudgov knexfile.js + - name: Install CF CLI + run: | + sudo curl -v -L -o cf8-cli-installer_8.7.4_x86-64.deb 'https://packages.cloudfoundry.org/stable?release=debian64&version=8.7.4' + sudo dpkg -i cf8-cli-installer_8.7.4_x86-64.deb + - name: Login to cloud.gov and deploy + run: | + set -e + # Log into cloud.gov + cf api api.fr.cloud.gov + cf login -u $CF_USERNAME -p $CF_PASSWORD -o $ORGANIZATION_NAME -s $SPACE_NAME + cat manifest.yml + cf push -f "./manifest.yml" + cf set-env analytics-reporter-api-develop API_DATA_GOV_SECRET "$API_DATA_GOV_SECRET" + cf restage analytics-reporter-api-develop + cf logout diff --git a/.mocharc.yml b/.mocharc.yml new file mode 100644 index 0000000..d050427 --- /dev/null +++ b/.mocharc.yml @@ -0,0 +1,11 @@ +diff: true +extension: ['js'] +package: './package.json' +slow: '75' +spec: + - 'test/**/*.js' +timeout: '2000' +ui: 'bdd' +watch-files: + - 'src/**/*.js' + - 'test/**/*.js' diff --git a/README.md b/README.md index d3bb810..633b087 100644 --- a/README.md +++ b/README.md @@ -149,21 +149,41 @@ An enum which describes the session. Possible values: 'Direct', 'Organic Search', 'Paid Social', 'Organic Social', 'Email', 'Affiliates', 'Referral', 'Paid Search', 'Video', and 'Display' -# Running the Tests +# Linting -The Analytics API application is backed by a test suite that uses -[Mocha](https://mochajs.org/) to run tests. +This repo uses Eslint and Prettier for code static analysis and formatting. Run +the linter with: -Before running the test suite, a database for the tests will need to be created. +```shell +npm run lint +``` + +Automatically fix lint issues with: ```shell -createdb analytics-api-test +npm run lint:fix ``` -Then the tests can be invoked via NPM. The test script has a pretest hook that -migrates the database. +# Running the unit tests + +The unit tests for this repo require a local PostgreSQL database. You can run a +local DB server or create a docker container using the provided test compose +file. (Requires docker and docker-compose to be installed) + +Starting a docker test DB: +```shell +docker-compose -f docker-compose.test.yml up ``` + +Once you have a PostgreSQL DB running locally, you can run the tests. The test +DB connection in knexfile.js has some default connection config which can be +overridden with environment variables. If using the provided docker-compose DB +then you can avoid setting the connection details. + +Run the tests (pre-test hook runs DB migrations): + +```shell npm test ``` diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..e889f41 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,10 @@ +version: "3.0" +services: + db: + image: postgres:16 + environment: + - POSTGRES_DB=analytics_reporter_test + - POSTGRES_USER=analytics + - POSTGRES_PASSWORD=123abc + ports: + - "5432:5432" diff --git a/entrypoint.sh b/entrypoint.sh index b3a796b..a9e69b9 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,4 @@ #!/bin/bash -export NEW_RELIC_APP_NAME="analytics-reporter-api" export PATH="$PATH:/home/vcap/deps/0/bin" npm run migrate npm start diff --git a/knexfile.js b/knexfile.js index 2090cb5..79bc1f1 100644 --- a/knexfile.js +++ b/knexfile.js @@ -26,8 +26,10 @@ module.exports = { test: { client: "postgresql", connection: { - user: process.env.CIRCLECI ? "postgres" : undefined, - database: "analytics-api-test", + host: process.env.POSTGRES_HOST || "localhost", + user: process.env.POSTGRES_USER || "analytics", + password: process.env.POSTGRES_PASSWORD || "123abc", + database: process.env.POSTGRES_DATABASE || "analytics_reporter_test", }, migrations: { tableName: "knex_migrations", diff --git a/manifest.yml b/manifest.yml index 426f6fd..d28f994 100644 --- a/manifest.yml +++ b/manifest.yml @@ -1,17 +1,17 @@ applications: -- name: analytics-reporter-api +- name: ${APP_NAME} instances: 1 memory: 128M buildpacks: - nodejs_buildpack command: "chmod +x ./entrypoint.sh && ./entrypoint.sh" services: - - analytics-reporter-database + - ${DB_NAME} stack: cflinuxfs4 env: API_DATA_GOV_SECRET: ${API_DATA_GOV_SECRET} NEW_RELIC_APP_NAME: ${NEW_RELIC_APP_NAME} NEW_RELIC_LICENSE_KEY: ${NEW_RELIC_LICENSE_KEY} - NODE_ENV: production - PGSSLMODE: true + NODE_ENV: production + PGSSLMODE: true diff --git a/package.json b/package.json index 2cedb16..9cc6c7e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dev": "nodemon src/index.js", "postinstall": "", "pretest": "NODE_ENV=test npm run migrate", - "test": "NODE_ENV=test `npm bin`/mocha test/**/*.test.js --exit", + "test": "NODE_ENV=test mocha", "migrate": "knex migrate:latest", "lint": "eslint .", "lint:fix": "eslint . --fix" diff --git a/src/db.js b/src/db.js index 894b512..44be3c5 100644 --- a/src/db.js +++ b/src/db.js @@ -125,4 +125,4 @@ const query = ({ ); }; -module.exports = { query, queryDomain, buildTimeQuery }; +module.exports = { query, queryDomain, buildTimeQuery, dbClient: db }; diff --git a/test/db.test.js b/test/db.test.js index 655785d..8485cfc 100644 --- a/test/db.test.js +++ b/test/db.test.js @@ -1,539 +1,577 @@ const expect = require("chai").expect; const proxyquire = require("proxyquire"); -const databaseSupport = require("./support/db"); +const database = require("./support/db"); const db = proxyquire("../src/db", { - "./config": databaseSupport.config, + "./config": require("../src/config"), }); -const routes = ["v1.1", "v2"]; +describe("db", () => { + const apiVersions = ["v1.1", "v2"]; -routes.forEach((route) => { - describe(`db with ${route}`, () => { - const table = route === "v1.1" ? "analytics_data" : "analytics_data_ga4"; - const queryVersion = route === `v1.1` ? "1.1" : "2"; + before((done) => { + // Setup the test database client + database.createClient().then(() => done()); + }); - beforeEach((done) => { - databaseSupport.resetSchema(table).then(() => done()); - }); + after((done) => { + // Clean up the test database client and the application database client + database + .destroyClient() + .then(() => { + return db.dbClient.destroy(); + }) + .then(() => done()); + }); - describe(".query(params)", () => { - it("should return all rows for the given agency and report", (done) => { - databaseSupport - .client(table) - .insert([ - { report_name: "my-report", report_agency: "my-agency" }, - { report_name: "not-my-report", report_agency: "my-agency" }, - { report_name: "my-report", report_agency: "not-my-agency" }, - { report_name: "my-report", report_agency: null }, - ]) - .then(() => { - return db.query({ - reportName: "my-report", - reportAgency: "my-agency", - version: queryVersion, - }); - }) - .then((results) => { - expect(results).to.have.length(1); - expect(results[0].report_name).to.equal("my-report"); - expect(results[0].report_agency).to.equal("my-agency"); - done(); - }) - .catch(done); - }); + apiVersions.forEach((apiVersion) => { + describe(`for API version ${apiVersion}`, () => { + const table = + apiVersion === "v1.1" ? "analytics_data" : "analytics_data_ga4"; + const queryVersion = apiVersion === `v1.1` ? "1.1" : "2"; - it("should return all rows without an agency if no agency name is given", (done) => { - databaseSupport - .client(table) - .insert([ - { report_name: "my-report", report_agency: "not-my-agency" }, - { report_name: "my-report", report_agency: null }, - ]) - .then(() => { - return db.query({ reportName: "my-report", version: queryVersion }); - }) - .then((results) => { - expect(results).to.have.length(1); - expect(results[0].report_name).to.equal("my-report"); - expect(results[0].report_agency).to.be.null; - done(); - }) - .catch(done); + beforeEach((done) => { + database.resetSchema(table).then(() => done()); }); - it("should sort the rows according to the date column", (done) => { - databaseSupport - .client(table) - .insert([ - { report_name: "report", date: "2017-01-02" }, - { report_name: "report", date: "2017-01-01" }, - { report_name: "report", date: "2017-01-03" }, - ]) - .then(() => { - return db.query({ reportName: "report", version: queryVersion }); - }) - .then((results) => { - expect(results).to.have.length(3); - results.forEach((result, index) => { - const resultDate = result.date.toISOString().slice(0, 10); - const expectedDate = `2017-01-0${3 - index}`; - expect(resultDate).to.equal(expectedDate); + describe(".query(params)", () => { + it("should return all rows for the given agency and report", (done) => { + database + .client(table) + .insert([ + { report_name: "my-report", report_agency: "my-agency" }, + { report_name: "not-my-report", report_agency: "my-agency" }, + { report_name: "my-report", report_agency: "not-my-agency" }, + { report_name: "my-report", report_agency: null }, + ]) + .then(() => { + return db.query({ + reportName: "my-report", + reportAgency: "my-agency", + version: queryVersion, + }); + }) + .then((results) => { + expect(results).to.have.length(1); + expect(results[0].report_name).to.equal("my-report"); + expect(results[0].report_agency).to.equal("my-agency"); + done(); + }) + .catch(done); + }); + + it("should return all rows without an agency if no agency name is given", (done) => { + database + .client(table) + .insert([ + { report_name: "my-report", report_agency: "not-my-agency" }, + { report_name: "my-report", report_agency: null }, + ]) + .then(() => { + return db.query({ + reportName: "my-report", + version: queryVersion, + }); + }) + .then((results) => { + expect(results).to.have.length(1); + expect(results[0].report_name).to.equal("my-report"); + expect(results[0].report_agency).to.be.null; + done(); + }) + .catch(done); + }); + + it("should sort the rows according to the date column", (done) => { + database + .client(table) + .insert([ + { report_name: "report", date: "2017-01-02" }, + { report_name: "report", date: "2017-01-01" }, + { report_name: "report", date: "2017-01-03" }, + ]) + .then(() => { + return db.query({ reportName: "report", version: queryVersion }); + }) + .then((results) => { + expect(results).to.have.length(3); + results.forEach((result, index) => { + const resultDate = result.date.toISOString().slice(0, 10); + const expectedDate = `2017-01-0${3 - index}`; + expect(resultDate).to.equal(expectedDate); + }); + done(); + }) + .catch(done); + }); + + it("should limit the rows according to the limit param", (done) => { + const rows = Array(5) + .fill(0) + .map(() => { + return { report_name: "report", date: "2017-01-01" }; }); - done(); - }) - .catch(done); - }); + database + .client(table) + .insert(rows) + .then(() => { + return db.query({ + reportName: "report", + limit: 4, + version: queryVersion, + }); + }) + .then((results) => { + expect(results).to.have.length(4); + done(); + }) + .catch(done); + }); - it("should limit the rows according to the limit param", (done) => { - const rows = Array(5) - .fill(0) - .map(() => { - return { report_name: "report", date: "2017-01-01" }; - }); - databaseSupport - .client(table) - .insert(rows) - .then(() => { - return db.query({ - reportName: "report", - limit: 4, - version: queryVersion, + it("should default to a limit of 1000", (done) => { + const rows = Array(1001) + .fill(0) + .map(() => { + return { report_name: "report", date: "2017-01-01" }; }); - }) - .then((results) => { - expect(results).to.have.length(4); - done(); - }) - .catch(done); + database + .client(table) + .insert(rows) + .then(() => { + return db.query({ reportName: "report", version: queryVersion }); + }) + .then((results) => { + expect(results).to.have.length(1000); + done(); + }) + .catch(done); + }); + + it("should have a maximum limit of 10,000", (done) => { + const rows = Array(11000) + .fill(0) + .map(() => { + return { report_name: "report", date: "2017-01-01" }; + }); + database + .client(table) + .insert(rows) + .then(() => { + return db.query({ + reportName: "report", + limit: 11000, + version: queryVersion, + }); + }) + .then((results) => { + expect(results).to.have.length(10000); + done(); + }) + .catch((err) => { + done(err); + }); + }); + + it("should paginate on the page param", (done) => { + const rows = Array(6) + .fill(0) + .map((val, index) => { + return { report_name: "report", date: `2017-01-0${index + 1}` }; + }); + database + .client(table) + .insert(rows) + .then(() => { + return db.query({ + reportName: "report", + limit: 3, + page: 1, + version: queryVersion, + }); + }) + .then((results) => { + expect(results).to.have.length(3); + expect(results[0].date.toISOString()).to.match(/^2017-01-06/); + expect(results[2].date.toISOString()).to.match(/^2017-01-04/); + + return db.query({ + reportName: "report", + limit: 3, + page: 2, + version: queryVersion, + }); + }) + .then((results) => { + expect(results).to.have.length(3); + expect(results[0].date.toISOString()).to.match(/^2017-01-03/); + expect(results[2].date.toISOString()).to.match(/^2017-01-01/); + done(); + }) + .catch(done); + }); }); - it("should default to a limit of 1000", (done) => { - const rows = Array(1001) - .fill(0) - .map(() => { - return { report_name: "report", date: "2017-01-01" }; - }); - databaseSupport - .client(table) - .insert(rows) - .then(() => { - return db.query({ reportName: "report", version: queryVersion }); - }) - .then((results) => { - expect(results).to.have.length(1000); - done(); - }) - .catch(done); + describe(".buildTimeQuery(before, after)", () => { + it("should return an array containing true if no date params are present", () => { + const result = db.buildTimeQuery(null, null); + expect(result).to.deep.equal([true]); + }); + + it("should return a nested array a raw query string and an array of the dates if both a params are set", () => { + const result = db.buildTimeQuery("2018-11-20", "2018-12-20"); + expect(result).to.deep.equal([ + '"date" <= ?::date AND "date" >= ?::date', + ["2018-11-20", "2018-12-20"], + ]); + }); + + it("should return a nested array a raw query string and an array of the before if before is set", () => { + const result = db.buildTimeQuery("2018-11-20", null); + expect(result).to.deep.equal(['"date" <= ?::date', ["2018-11-20"]]); + }); + + it("should return a nested array a raw query string and an array of the after if after is set", () => { + const result = db.buildTimeQuery(null, "2018-11-22"); + expect(result).to.deep.equal(['"date" >= ?::date', ["2018-11-22"]]); + }); }); - it("should have a maximum limit of 10,000", (done) => { - const rows = Array(11000) - .fill(0) - .map(() => { - return { report_name: "report", date: "2017-01-01" }; - }); - databaseSupport - .client(table) - .insert(rows) - .then(() => { - return db.query({ - reportName: "report", - limit: 11000, - version: queryVersion, + describe(".queryDomain(params)", () => { + it("should only return 2 results that include site reports from the test.gov domain", (done) => { + database + .client(table) + .insert([ + { + report_name: "site", + date: "2017-01-02", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-01", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-03", + data: { domain: "test.gov" }, + }, + ]) + .then(() => { + return db.queryDomain( + "test.gov", + "site", + 2, + 1, + null, + null, + table, + ); + }) + .then((results) => { + expect(results).to.have.length(2); + done(); + }) + .catch((err) => { + done(err); }); - }) - .then((results) => { - expect(results).to.have.length(10000); - done(); - }) - .catch((err) => { - done(err); - }); - }); + }); - it("should paginate on the page param", (done) => { - const rows = Array(6) - .fill(0) - .map((val, index) => { - return { report_name: "report", date: `2017-01-0${index + 1}` }; - }); - databaseSupport - .client(table) - .insert(rows) - .then(() => { - return db.query({ - reportName: "report", - limit: 3, - page: 1, - version: queryVersion, + it("should only return 2 results that include site reports from the test.gov domain, when multiple reports", (done) => { + database + .client(table) + .insert([ + { + report_name: "report", + date: "2017-01-02", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-01", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-03", + data: { domain: "test.gov" }, + }, + ]) + .then(() => { + return db.queryDomain( + "test.gov", + "site", + 1000, + 1, + null, + null, + table, + ); + }) + .then((results) => { + expect(results).to.have.length(2); + expect(results[0].report_name).to.equal("site"); + expect(results[0].data.domain).to.equal("test.gov"); + done(); + }) + .catch((err) => { + done(err); }); - }) - .then((results) => { - expect(results).to.have.length(3); - expect(results[0].date.toISOString()).to.match(/^2017-01-06/); - expect(results[2].date.toISOString()).to.match(/^2017-01-04/); + }); - return db.query({ - reportName: "report", - limit: 3, - page: 2, - version: queryVersion, + it("should only return 2 results that include site reports from the test.gov domain, when multiple domains", (done) => { + database + .client(table) + .insert([ + { + report_name: "site", + date: "2017-01-02", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-01", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-03", + data: { domain: "usda.gov" }, + }, + ]) + .then(() => { + return db.queryDomain( + "test.gov", + "site", + 1000, + 1, + null, + null, + table, + ); + }) + .then((results) => { + expect(results).to.have.length(2); + expect(results[0].report_name).to.equal("site"); + expect(results[0].data.domain).to.equal("test.gov"); + done(); + }) + .catch((err) => { + done(err); }); - }) - .then((results) => { - expect(results).to.have.length(3); - expect(results[0].date.toISOString()).to.match(/^2017-01-03/); - expect(results[2].date.toISOString()).to.match(/^2017-01-01/); - done(); - }) - .catch(done); - }); - }); + }); - describe(".buildTimeQuery(before, after)", () => { - it("should return an array containing true if no date params are present", () => { - const result = db.buildTimeQuery(null, null); - expect(result).to.deep.equal([true]); - }); - it("should return a nested array a raw query string and an array of the dates if both a params are set", () => { - const result = db.buildTimeQuery("2018-11-20", "2018-12-20"); - expect(result).to.deep.equal([ - '"date" <= ?::date AND "date" >= ?::date', - ["2018-11-20", "2018-12-20"], - ]); - }); - it("should return a nested array a raw query string and an array of the before if before is set", () => { - const result = db.buildTimeQuery("2018-11-20", null); - expect(result).to.deep.equal(['"date" <= ?::date', ["2018-11-20"]]); - }); - it("should return a nested array a raw query string and an array of the after if after is set", () => { - const result = db.buildTimeQuery(null, "2018-11-22"); - expect(result).to.deep.equal(['"date" >= ?::date', ["2018-11-22"]]); - }); - }); - describe(".queryDomain(params)", () => { - it("should only return 2 results that include site reports from the test.gov domain", (done) => { - databaseSupport - .client(table) - .insert([ - { - report_name: "site", - date: "2017-01-02", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-01", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-03", - data: { domain: "test.gov" }, - }, - ]) - .then(() => { - return db.queryDomain("test.gov", "site", 2, 1, null, null, table); - }) - .then((results) => { - expect(results).to.have.length(2); - done(); - }) - .catch((err) => { - done(err); - }); - }); - it("should only return 2 results that include site reports from the test.gov domain, when multiple reports", (done) => { - databaseSupport - .client(table) - .insert([ - { - report_name: "report", - date: "2017-01-02", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-01", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-03", - data: { domain: "test.gov" }, - }, - ]) - .then(() => { - return db.queryDomain( - "test.gov", - "site", - 1000, - 1, - null, - null, - table, - ); - }) - .then((results) => { - expect(results).to.have.length(2); - expect(results[0].report_name).to.equal("site"); - expect(results[0].data.domain).to.equal("test.gov"); - done(); - }) - .catch((err) => { - done(err); - }); - }); - it("should only return 2 results that include site reports from the test.gov domain, when multiple domains", (done) => { - databaseSupport - .client(table) - .insert([ - { - report_name: "site", - date: "2017-01-02", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-01", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-03", - data: { domain: "usda.gov" }, - }, - ]) - .then(() => { - return db.queryDomain( - "test.gov", - "site", - 1000, - 1, - null, - null, - table, - ); - }) - .then((results) => { - expect(results).to.have.length(2); - expect(results[0].report_name).to.equal("site"); - expect(results[0].data.domain).to.equal("test.gov"); - done(); - }) - .catch((err) => { - done(err); - }); - }); + it("should only return 2 results that include site reports from the test.gov domain, when before date parameters are in", (done) => { + database + .client(table) + .insert([ + { + report_name: "site", + date: "2017-01-02", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-01", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "usda.gov" }, + }, + ]) + .then(() => { + return db.queryDomain( + "test.gov", + "site", + 1000, + 1, + "2017-10-20", + null, + table, + ); + }) + .then((results) => { + expect(results).to.have.length(2); + expect(results[0].report_name).to.equal("site"); + expect(results[0].data.domain).to.equal("test.gov"); + expect(results[0].date.toISOString()).to.match(/^2017-01-02/); + done(); + }) + .catch((err) => { + done(err); + }); + }); - it("should only return 2 results that include site reports from the test.gov domain, when before date parameters are in", (done) => { - databaseSupport - .client(table) - .insert([ - { - report_name: "site", - date: "2017-01-02", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-01", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "usda.gov" }, - }, - ]) - .then(() => { - return db.queryDomain( - "test.gov", - "site", - 1000, - 1, - "2017-10-20", - null, - table, - ); - }) - .then((results) => { - expect(results).to.have.length(2); - expect(results[0].report_name).to.equal("site"); - expect(results[0].data.domain).to.equal("test.gov"); - expect(results[0].date.toISOString()).to.match(/^2017-01-02/); - done(); - }) - .catch((err) => { - done(err); - }); - }); - it("should only return 1 result that include site reports from the test.gov domain, when after date parameters are in", (done) => { - databaseSupport - .client(table) - .insert([ - { - report_name: "site", - date: "2017-01-02", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-01", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "usda.gov" }, - }, - ]) - .then(() => { - return db.queryDomain( - "test.gov", - "site", - 1000, - 1, - null, - "2017-10-20", - table, - ); - }) - .then((results) => { - expect(results).to.have.length(1); - expect(results[0].report_name).to.equal("site"); - expect(results[0].data.domain).to.equal("test.gov"); - expect(results[0].date.toISOString()).to.match(/^2018-01-03/); - done(); - }) - .catch((err) => { - done(err); - }); - }); - it("should only return 2 result that include site reports from the test.gov domain, when after/before date parameters set", (done) => { - databaseSupport - .client(table) - .insert([ - { - report_name: "site", - date: "2017-01-02", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-01", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-11-04", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-11-03", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "usda.gov" }, - }, - ]) - .then(() => { - return db.queryDomain( - "test.gov", - "site", - 1000, - 1, - "2018-01-02", - "2017-10-20", - table, - ); - }) - .then((results) => { - expect(results).to.have.length(2); - expect(results[0].report_name).to.equal("site"); - expect(results[0].data.domain).to.equal("test.gov"); - expect(results[0].date.toISOString()).to.match(/^2017-11-04/); - done(); - }) - .catch((err) => { - done(err); - }); - }); - it("should only return 2 result that include site reports from the test.gov domain, when after/before date parameters set", (done) => { - databaseSupport - .client(table) - .insert([ - { - report_name: "site", - date: "2017-01-02", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-01-01", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "test.gov" }, - }, - { - report_name: "report", - date: "2018-01-03", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2017-11-03", - data: { domain: "test.gov" }, - }, - { - report_name: "site", - date: "2018-01-03", - data: { domain: "usda.gov" }, - }, - ]) - .then(() => { - return db.queryDomain( - "test.gov", - "site", - 1000, - 1, - "2018-01-04", - "2017-10-20", - table, - ); - }) - .then((results) => { - expect(results).to.have.length(2); - expect(results[0].report_name).to.equal("site"); - expect(results[0].data.domain).to.equal("test.gov"); - expect(results[0].date.toISOString()).to.match(/^2018-01-03/); - done(); - }) - .catch((err) => { - done(err); - }); + it("should only return 1 result that include site reports from the test.gov domain, when after date parameters are in", (done) => { + database + .client(table) + .insert([ + { + report_name: "site", + date: "2017-01-02", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-01", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "usda.gov" }, + }, + ]) + .then(() => { + return db.queryDomain( + "test.gov", + "site", + 1000, + 1, + null, + "2017-10-20", + table, + ); + }) + .then((results) => { + expect(results).to.have.length(1); + expect(results[0].report_name).to.equal("site"); + expect(results[0].data.domain).to.equal("test.gov"); + expect(results[0].date.toISOString()).to.match(/^2018-01-03/); + done(); + }) + .catch((err) => { + done(err); + }); + }); + + it("should only return 2 result that include site reports from the test.gov domain, when after/before date parameters set", (done) => { + database + .client(table) + .insert([ + { + report_name: "site", + date: "2017-01-02", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-01", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-11-04", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-11-03", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "usda.gov" }, + }, + ]) + .then(() => { + return db.queryDomain( + "test.gov", + "site", + 1000, + 1, + "2018-01-02", + "2017-10-20", + table, + ); + }) + .then((results) => { + expect(results).to.have.length(2); + expect(results[0].report_name).to.equal("site"); + expect(results[0].data.domain).to.equal("test.gov"); + expect(results[0].date.toISOString()).to.match(/^2017-11-04/); + done(); + }) + .catch((err) => { + done(err); + }); + }); + + it("should only return 2 result that include site reports from the test.gov domain, when after/before date parameters set", (done) => { + database + .client(table) + .insert([ + { + report_name: "site", + date: "2017-01-02", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-01-01", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "test.gov" }, + }, + { + report_name: "report", + date: "2018-01-03", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2017-11-03", + data: { domain: "test.gov" }, + }, + { + report_name: "site", + date: "2018-01-03", + data: { domain: "usda.gov" }, + }, + ]) + .then(() => { + return db.queryDomain( + "test.gov", + "site", + 1000, + 1, + "2018-01-04", + "2017-10-20", + table, + ); + }) + .then((results) => { + expect(results).to.have.length(2); + expect(results[0].report_name).to.equal("site"); + expect(results[0].data.domain).to.equal("test.gov"); + expect(results[0].date.toISOString()).to.match(/^2018-01-03/); + done(); + }) + .catch((err) => { + done(err); + }); + }); }); }); }); diff --git a/test/support/db.js b/test/support/db.js index fa86c4a..13ad18d 100644 --- a/test/support/db.js +++ b/test/support/db.js @@ -1,10 +1,31 @@ const knex = require("knex"); const config = require("../../src/config"); -const client = knex({ client: "pg", connection: config.postgres }); +class Database { + get client() { + return this.dbClient; + } -const resetSchema = (table) => { - return client(table).delete(); -}; + async createClient() { + if (this.dbClient) { + return; + } -module.exports = { client, config, resetSchema }; + this.dbClient = await knex({ client: "pg", connection: config.postgres }); + } + + async destroyClient() { + if (this.dbClient) { + await this.dbClient.destroy(); + this.dbClient = null; + } + + return; + } + + resetSchema(table) { + return this.dbClient(table).delete(); + } +} + +module.exports = new Database();