Skip to content

Commit

Permalink
feat: website getter methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Marbach committed Mar 30, 2020
1 parent bb93304 commit d13f430
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 90 deletions.
24 changes: 13 additions & 11 deletions src/api/audits/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ export async function retrieveAuditList(
conn: DbConnectionType,
options: ListRequest = {},
): Promise<Audit[]> {
const res = await conn.query<AuditRow>(
addListRequestToQuery(
SQL`SELECT * FROM lighthouse_audits ORDER BY time_created DESC`,
options,
),
);
let query = SQL`SELECT * FROM lighthouse_audits`;
if (options.where) {
query = query.append(SQL`\n`);
query = query.append(options.where);
}
query = query.append(SQL`\nORDER BY time_created DESC`);
query = addListRequestToQuery(query, options);
const res = await conn.query<AuditRow>(query);
return res.rows.map(Audit.buildForDbRow);
}

Expand All @@ -64,12 +66,12 @@ export async function retrieveAuditById(
conn: DbConnectionType,
auditId: string,
): Promise<Audit> {
const res = await conn.query<AuditRow>(SQL`
SELECT * FROM lighthouse_audits WHERE id = ${auditId};
`);
if (res.rowCount === 0)
const res = await retrieveAuditList(conn, {
where: SQL`WHERE id = ${auditId}`,
});
if (res.length === 0)
throw new NotFoundError(`audit not found for id "${auditId}"`);
return Audit.buildForDbRow(res.rows[0]);
return res[0];
}

export async function deleteAuditById(
Expand Down
52 changes: 26 additions & 26 deletions src/api/audits/methods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,32 @@ describe('audit methods', () => {
conn.end();
});

describe('#getAudit', () => {
let audit: Audit;

beforeEach(() => {
audit = Audit.buildForUrl('https://spotify.com');
db.retrieveAuditById.mockResolvedValueOnce(audit);
});

it('returns the retrieved audit', async () => {
await expect(getAudit(conn, audit.id)).resolves.toMatchObject(audit);
});
});

describe('#deleteAudit', () => {
let audit: Audit;

beforeEach(() => {
audit = Audit.buildForUrl('https://spotify.com');
db.deleteAuditById.mockResolvedValueOnce(audit);
});

it('returns the deleted audit', async () => {
await expect(deleteAudit(conn, audit.id)).resolves.toMatchObject(audit);
});
});

describe('#triggerAudit', () => {
// ensure that we wait for any background writes that are still occurring wrap up
// so that they don't infect other tests.
Expand Down Expand Up @@ -210,19 +236,6 @@ describe('audit methods', () => {
});
});

describe('#getAudit', () => {
let audit: Audit;

beforeEach(() => {
audit = Audit.buildForUrl('https://spotify.com');
db.retrieveAuditById.mockResolvedValueOnce(audit);
});

it('returns the retrieved audit', async () => {
await expect(getAudit(conn, audit.id)).resolves.toMatchObject(audit);
});
});

describe('#getAuditList', () => {
let audit: Audit;

Expand All @@ -241,17 +254,4 @@ describe('audit methods', () => {
});
});
});

describe('#deleteAudit', () => {
let audit: Audit;

beforeEach(() => {
audit = Audit.buildForUrl('https://spotify.com');
db.deleteAuditById.mockResolvedValueOnce(audit);
});

it('returns the deleted audit', async () => {
await expect(deleteAudit(conn, audit.id)).resolves.toMatchObject(audit);
});
});
});
27 changes: 6 additions & 21 deletions src/api/audits/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ import waitOn from 'wait-on';
import puppeteer from 'puppeteer';

import { Audit, AuditListItem } from './models';
import {
persistAudit,
retrieveAuditById,
retrieveAuditList,
deleteAuditById,
retrieveAuditCount,
} from './db';
import { persistAudit, retrieveAuditList, retrieveAuditCount } from './db';
import parentLogger from '../../logger';
import { DbConnectionType } from '../../db';
import { InvalidRequestError } from '../../errors';
Expand All @@ -21,6 +15,11 @@ const DEFAULT_CHROME_PATH = process.env.CHROME_PATH;

const HTTP_RE = /^https?:\/\//;

export {
retrieveAuditById as getAudit,
deleteAuditById as deleteAudit,
} from './db';

export interface AuditOptions {
awaitAuditCompleted?: boolean;
upTimeout?: number;
Expand Down Expand Up @@ -151,21 +150,7 @@ async function runAudit(
return audit;
}

export async function getAudit(
conn: DbConnectionType,
auditId: string,
): Promise<Audit> {
return await retrieveAuditById(conn, auditId);
}

export const getAudits = listResponseFactory<AuditListItem>(
async (...args) => (await retrieveAuditList(...args)).map(a => a.listItem),
retrieveAuditCount,
);

export async function deleteAudit(
conn: DbConnectionType,
auditId: string,
): Promise<Audit> {
return await deleteAuditById(conn, auditId);
}
1 change: 1 addition & 0 deletions src/api/listHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SQL, { SQLStatement } from 'sql-template-strings';
export interface ListRequest {
limit?: number;
offset?: number;
where?: SQLStatement;
}

export interface ListResponse<Item> extends ListRequest {
Expand Down
83 changes: 82 additions & 1 deletion src/api/websites/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import SQL from 'sql-template-strings';
import { Pool } from 'pg';

import { retrieveWebsiteList, retrieveWebsiteTotal } from './db';
import {
retrieveWebsiteList,
retrieveWebsiteTotal,
retrieveWebsiteByUrl,
retrieveWebsiteByAuditId,
} from './db';
import { Audit } from '../audits';
import { persistAudit } from '../audits/db';
import { NotFoundError } from '../../errors';

async function wait(ms: number = 0): Promise<void> {
await new Promise(r => setTimeout(r, ms));
Expand All @@ -28,6 +34,81 @@ describe('audit db methods', () => {
conn.end();
});

describe('#retrieveWebsiteByUrl', () => {
beforeEach(async () => {
const first = persistAudit(
conn,
Audit.buildForUrl('https://spotify.com/se'),
);
await wait(10);
const middle = persistAudit(
conn,
Audit.buildForUrl('https://spotify.com/gb'),
);
await wait(10);
const last = persistAudit(
conn,
Audit.buildForUrl('https://spotify.com/en'),
);
await wait(10);
const last2 = persistAudit(
conn,
Audit.buildForUrl('https://spotify.com/en'),
);
await wait(10);
const last3 = persistAudit(
conn,
Audit.buildForUrl('https://spotify.com/en'),
);
await Promise.all([first, middle, last, last2, last3]);

await conn.query(SQL`COMMIT;`);
});

it('returns the website', async () => {
const website = await retrieveWebsiteByUrl(
conn,
'https://spotify.com/en',
);
expect(website.url).toBe('https://spotify.com/en');
expect(website.audits).toHaveLength(3);
expect(website.lastAudit).toBeInstanceOf(Object);
});

describe('when url doesnt exist', () => {
it('throws a NotFoundError', async () => {
await expect(
retrieveWebsiteByUrl(conn, 'https://spotify.com/de'),
).rejects.toThrowError(NotFoundError);
});
});
});

describe('#retrieveWebsiteByAuditId', () => {
let audit: Audit;

beforeEach(async () => {
audit = Audit.buildForUrl('https://spotify.com/se');
await persistAudit(conn, audit);
await conn.query(SQL`COMMIT;`);
});

it('returns the website', async () => {
const website = await retrieveWebsiteByAuditId(conn, audit.id);
expect(website.url).toBe('https://spotify.com/se');
expect(website.audits).toHaveLength(3);
expect(website.lastAudit).toBeInstanceOf(Object);
});

describe('when url doesnt exist', () => {
it('throws a NotFoundError', async () => {
await expect(
retrieveWebsiteByUrl(conn, 'https://spotify.com/de'),
).rejects.toThrowError(NotFoundError);
});
});
});

describe('#retrieveWebsiteList', () => {
beforeEach(async () => {
const first = persistAudit(
Expand Down
68 changes: 43 additions & 25 deletions src/api/websites/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,62 @@ import SQL from 'sql-template-strings';
import { Website } from './models';
import { DbConnectionType } from '../../db';
import { ListRequest, addListRequestToQuery } from '../listHelpers';
import { NotFoundError } from '../../errors';

export interface WebsiteRow {
url: string;
time_last_created: Date;
audits_json: string[];
}

// TODO retrieve website by url
// export async function retrieveWebsiteByUrl(conn: DbConnectionType, url: string): Promise<Website> {

// }
export async function retrieveWebsiteByUrl(
conn: DbConnectionType,
url: string,
): Promise<Website> {
const res = await retrieveWebsiteList(conn, {
where: SQL`WHERE url = ${url}`,
});
if (res.length === 0)
throw new NotFoundError(`no audited website found for url "${url}"`);
return res[0];
}

// TODO retrieve website from audit id
export async function retrieveWebsiteByAuditId(
conn: DbConnectionType,
auditId: string,
): Promise<Website> {
const res = await retrieveWebsiteList(conn, {
where: SQL`WHERE url IN (SELECT url FROM lighthouse_audits w WHERE w.id = ${auditId})`,
});
if (res.length === 0)
throw new NotFoundError(`website found for audit id "${auditId}"`);
return res[0];
}

export async function retrieveWebsiteList(
conn: DbConnectionType,
options: ListRequest,
): Promise<Website[]> {
const queryResult = await conn.query<WebsiteRow>(
addListRequestToQuery(
SQL`
SELECT
distinct url,
MAX(time_created) as time_last_created,
array(
SELECT to_json(n.*)::text
FROM lighthouse_audits n
WHERE n.url = o.url
ORDER BY time_created DESC
) as audits_json
FROM lighthouse_audits o
GROUP BY url
ORDER BY time_last_created DESC
`,
options,
),
);

let query = SQL`
SELECT
distinct url,
MAX(time_created) as time_last_created,
array(
SELECT to_json(n.*)::text
FROM lighthouse_audits n
WHERE n.url = o.url
ORDER BY time_created DESC
) as audits_json
FROM lighthouse_audits o
`;
if (options.where) {
query.append(`\n`);
query.append(options.where);
}
query = query.append(SQL`\nGROUP BY url`);
query = query.append(SQL`\nORDER BY time_last_created DESC`);
query = addListRequestToQuery(query, options);
const queryResult = await conn.query<WebsiteRow>(query);
return queryResult.rows.map(Website.buildForDbRow);
}

Expand Down
Loading

0 comments on commit d13f430

Please sign in to comment.