import {dbDateFromString, dbDateToday, prisma} from './db.js'; import {newAppForTest, resetDB, TEST_PW} from './test-helpers.js'; import * as request from 'supertest'; import { ADMIN_DOWNLOAD_PATH, ADMIN_LICENSE_PATH, ADMIN_POPULATE_PATH, ADMIN_PATH, ADMIN_LICENSES_ACTIVE_PATH, ADMIN_SIGNUPS_PATH, } from './api-types.js'; import {expect} from 'chai'; import {suite, test} from 'mocha'; import { activesForLicenseInWindow, activesInWindow, cleanForCSV, populateActives, } from './admin.js'; import type {ActivesInWindowResult} from './admin.js'; import {newLogContext} from './logger.js'; const app = newAppForTest(); suite('Server: Admin', () => { test('popualte', async () => { await resetDB(prisma); expect(await prisma.active.count()).to.equal(0); const response = await request .default(app) .get(ADMIN_POPULATE_PATH) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(200); expect(await prisma.active.count()).gt(0); }); suite('index', () => { test('exists', async () => { const response = await request .default(app) .get(ADMIN_PATH) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(200); expect(response.text).to.include(ADMIN_DOWNLOAD_PATH); expect(response.text).to.include(ADMIN_LICENSE_PATH.replace('/:key', '')); expect(response.text).to.include(ADMIN_LICENSES_ACTIVE_PATH); expect(response.text).to.include(ADMIN_SIGNUPS_PATH); expect(response.text).to.include(ADMIN_POPULATE_PATH); }); }); suite('licenseView', () => { test('no such license 404s', async () => { const response = await request .default(app) .get(ADMIN_LICENSE_PATH.replace(':key', 'no-such-license')) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(404); }); test('license exists', async () => { await resetDB(prisma); await populate(); const response = await request .default(app) .get(ADMIN_LICENSE_PATH.replace(':key', 'key-1')) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(200); expect(response.text).to.include('name-1'); const now = (await dbDateToday()).toISOString().slice(0, 7); expect(response.text).to.match(new RegExp(`${now}: \\d+`)); }); }); suite('newSignups', () => { test('requires auth', async () => { const response = await request .default(app) .get(ADMIN_SIGNUPS_PATH) .send(); expect(response.statusCode).to.equal(401); }); test('populated', async () => { await resetDB(prisma); await populate(); const response = await request .default(app) .get(ADMIN_SIGNUPS_PATH) .query({n: '30'}) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(200); const re = new RegExp( `name-3.*2.*` + `name-2.*1.*` + `name-1.*0`, ); expect(response.text).to.match( re, `Didn't match ${re}: ${response.text}`, ); }); }); suite('activesForLicenseInWindow', () => { test('no actives', async () => { await resetDB(prisma); expect( await activesForLicenseInWindow( 'no-such-key', dbDateFromString('2000-01-01'), dbDateFromString('2000-01-01)'), newLogContext('info'), ), ).to.equal(0); }); test('has actives', async () => { await resetDB(prisma); await populateActives(prisma, [ { licenseKey: 'key-1', profiles: [ { profileID: 'profile-1', dates: ['2000-01-01', '2000-01-01', '2000-01-02'], }, { profileID: 'profile-2', dates: ['2000-01-01', '2000-01-01', '2000-01-02'], }, { profileID: 'profile-3', dates: ['2000-01-02', '2000-01-03'], }, ], }, { licenseKey: 'key-2', profiles: [ { profileID: 'profile-4', dates: ['2000-01-01', '2000-01-01', '2000-01-02'], }, ], }, ]); expect( await activesForLicenseInWindow( 'no-such-key', dbDateFromString('2000-01-01'), dbDateFromString('2000-01-01)'), newLogContext('info'), ), ).to.equal(0); expect( await activesForLicenseInWindow( 'key-1', dbDateFromString('2000-01-01'), dbDateFromString('2000-01-01)'), newLogContext('info'), ), ).to.equal(2); expect( await activesForLicenseInWindow( 'key-1', dbDateFromString('2000-01-01'), dbDateFromString('2000-01-02)'), newLogContext('info'), ), ).to.equal(3); expect( await activesForLicenseInWindow( 'key-1', dbDateFromString('2000-01-03'), dbDateFromString('2000-01-10)'), newLogContext('info'), ), ).to.equal(1); }); }); suite('activesInWindow', () => { test('no actives', async () => { await resetDB(prisma); const got: ActivesInWindowResult[] = await activesInWindow( dbDateFromString('2000-01-01'), dbDateFromString('2000-01-01)'), ); expect(got.length).to.equal(0); }); test('has actives', async () => { await resetDB(prisma); const customerIds = await populateActives(prisma, [ { licenseKey: 'key-1', profiles: [ { profileID: 'profile-1', dates: ['2000-01-01', '2000-01-01', '2000-01-02'], }, { profileID: 'profile-2', dates: ['2000-01-01', '2000-01-01', '2000-01-02'], }, { profileID: 'profile-3', dates: ['2000-01-02', '2000-01-03'], }, ], }, { licenseKey: 'key-2', profiles: [ { profileID: 'profile-4', dates: ['2000-01-01', '2000-01-01', '2000-01-02'], }, ], }, ]); type ActivesInWindowTestCase = { start: string; end: string; expected: { customerId: number; customerName: string; email: string; project: string | null; commercial: boolean; licenseKey: string; cnt: number; }[]; }; async function activesInWindowTest(tc: ActivesInWindowTestCase) { const got = await activesInWindow( dbDateFromString(tc.start), dbDateFromString(tc.end), ); expect(got, `expected ${tc.expected}, got ${got}`).to.deep.equal( tc.expected, ); } await activesInWindowTest({ start: '2000-01-01', end: '2000-01-01', expected: [ { customerId: customerIds[0], customerName: 'some customer', email: 'fake@example.com', project: null, commercial: true, licenseKey: 'key-1', cnt: 2, }, { customerId: customerIds[1], customerName: 'some customer', email: 'fake@example.com', project: null, commercial: true, licenseKey: 'key-2', cnt: 1, }, ], }); await activesInWindowTest({ start: '2000-01-01', end: '2000-01-02', expected: [ { customerId: customerIds[0], customerName: 'some customer', email: 'fake@example.com', project: null, commercial: true, licenseKey: 'key-1', cnt: 3, }, { customerId: customerIds[1], customerName: 'some customer', email: 'fake@example.com', project: null, commercial: true, licenseKey: 'key-2', cnt: 1, }, ], }); await activesInWindowTest({ start: '2000-01-03', end: '2000-01-10', expected: [ { customerId: customerIds[0], customerName: 'some customer', email: 'fake@example.com', project: null, commercial: true, licenseKey: 'key-1', cnt: 1, }, ], }); }); }); suite('downloadActives', () => { test('requires auth', async () => { const response = await request .default(app) .get(ADMIN_DOWNLOAD_PATH) .send(); expect(response.statusCode).to.equal(401); }); test('empty db', async () => { await resetDB(prisma); const response = await request .default(app) .get(ADMIN_DOWNLOAD_PATH) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(200); expect(response.header['content-type']).to.equal( 'text/csv; charset=utf-8', ); expect(response.header['content-disposition']).to.equal( 'attachment; filename=actives.csv', ); // Brittle yes, but easy. expect(response.text).to.equal( 'customerId,name,email,project,commercial,status,key,profileID,pingDate', ); }); test('populated db', async () => { await resetDB(prisma); const customerIds = await populate(); const response = await request .default(app) .get(ADMIN_DOWNLOAD_PATH) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(200); // Brittle yes, but easy. const expected = `customerId,name,email,project,commercial,status,key,profileID,pingDate\n` + `${customerIds[0]},name-1,fake@example.com,,true,VALID,key-1,,\n` + `${customerIds[1]},name-2,fake@example.com,,true,VALID,key-2,test-profile-2-0,2022-01-01\n` + `${customerIds[2]},name-3,fake@example.com,,true,VALID,key-3,test-profile-3-0,2022-01-01\n` + `${customerIds[2]},name-3,fake@example.com,,true,VALID,key-3,test-profile-3-1,2022-01-01`; expect(response.text).to.equal(expected); }); test('cleans field values', async () => { await resetDB(prisma); await prisma.license.create({ data: { key: 'key', status: 'VALID', customer: { create: { name: 'Rocicorp, LLC', project: 'Rocicorp, LLC', email: 'foo@example.com', }, }, }, }); const response = await request .default(app) .get(ADMIN_DOWNLOAD_PATH) .auth('admin', TEST_PW) .send(); expect(response.statusCode).to.equal(200); const re = /Rocicorp_ LLC.*Rocicorp_ LLC,/; expect(response.text).to.match( re, `expected ${response.text} to match ${re}`, ); }); test('cleanForCSV', () => { expect(cleanForCSV('foo')).to.equal('foo'); expect(cleanForCSV('foo,bar,baz')).to.equal('foo_bar_baz'); expect(cleanForCSV('foo\nbar')).to.equal('foo_bar'); expect(cleanForCSV('http://foo.com:80/bar?baz')).to.equal( 'http://foo.com:80/bar?baz', ); expect(cleanForCSV('foo@bar.com')).to.equal('foo@bar.com'); expect(cleanForCSV('=DANGER(foo)')).to.equal('_DANGER_foo_'); }); }); }); // Generates three licenses: one with zero pings (customerId 1), one with one // ping (customerId 2), and one with two pings (customerId 3). The joined table // and resulting ouput should look like this: // // customerId name email project commercial status key profileID date // id1 name-1 "" null true VALID key-1 // id2 name-2 "" null true VALID key-2 test-profile-2-0 2022-01-01 // id3 name-3 "" null true VALID key-3 test-profile-3-0 2022-01-01 // id3 name-3 "" null true VALID key-3 test-profile-3-1 2022-01-01 // // TODO this dups functionality with populateActives in test helpers, // we should just use that. (This code predates that helper.) async function populate() { expect(await prisma.license.count()).to.equal(0); const customerIds = []; for (let i = 1; i < 4; i++) { const license = await prisma.license.create({ data: { key: 'key-' + i, status: 'VALID', commercial: true, acceptedTOS: true, customer: { create: { name: 'name-' + i, email: 'fake@example.com', }, }, }, }); customerIds.push(license.customerId); for (let actives = 0; actives < i - 1; actives++) { await dbDateToday(); await prisma.active.create({ data: { date: dbDateFromString('2022-01-01'), licenseKey: license.key, profileID: `test-profile-${i}-${actives}`, }, }); } } expect(await prisma.license.count()).to.equal(3); return customerIds; }