Skip to content

Commit

Permalink
More, relatively easy TS conversion (#104)
Browse files Browse the repository at this point in the history
The only substantive changes are:

- In `incentive-calculation.test.ts`, where the calls to `_.countBy`
  were missing an argument and thus not counting anything.

- In `v0.test.js`, where `qs` is used to encode GET params, because
  it's much less picky about what types you give it.

The only file left now is `src/routes/v0.js`, which won't be trivial,
so I'm doing it separately.
  • Loading branch information
oyamauchi authored Aug 3, 2023
1 parent 0b55311 commit 18f6cc4
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 39 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"lint": "prettier --check . && eslint .",
"deploy:dev": "gcloud run deploy incentives-api-dev --project rewiring-america-dev --source .",
"deploy:production": "gcloud run deploy incentives-api --project rewiring-america --source .",
"test": "yarn lint && dotenv -- tap --node-arg=--no-warnings --node-arg=--loader=ts-node/esm \"test/**/*.test.{js,ts}\""
"test": "yarn lint && dotenv -- tap --node-arg=--no-warnings --node-arg=--loader=ts-node/esm \"test/**/*.test.ts\""
},
"type": "module"
}
2 changes: 2 additions & 0 deletions src/@types/fastify-cli.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Trivial module declaration to satisfy tsc
declare module 'fastify-cli/helper.js';
2 changes: 1 addition & 1 deletion test/helper.js → test/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function config() {
}

// automatically build and tear down our instance
async function build(t) {
async function build(t: Tap.Test) {
// you can set all the options supported by the fastify CLI command
const argv = [AppPath, '--options'];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ beforeEach(async t => {
driver: sqlite3.Database,
});
t.context.oldGeocode = geocoder.geocode;
geocoder.geocode = function (query, fields) {
geocoder.geocode = function (query: string, fields: string[]) {
return mockGeocoder.geocode(query, fields);
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ afterEach(async t => {

test('correctly evaluates scenerio "Single w/ $120k Household income"', async t => {
const data = calculateIncentives(AMIS_FOR_11211, {
zip: '11211',
location: { zip: '11211' },
owner_status: 'homeowner',
household_income: 120000,
tax_filing: 'single',
Expand All @@ -39,7 +39,7 @@ test('correctly evaluates scenerio "Single w/ $120k Household income"', async t

test('correctly evaluates scenerio "Joint w/ 5 persons and $60k Household income"', async t => {
const data = calculateIncentives(AMIS_FOR_11211, {
zip: '11211',
location: { zip: '11211' },
owner_status: 'homeowner',
household_income: 60000,
tax_filing: 'joint',
Expand All @@ -50,7 +50,7 @@ test('correctly evaluates scenerio "Joint w/ 5 persons and $60k Household income

test('correctly evaluates scenerio "Joint w/ $300k Household income"', async t => {
const data = calculateIncentives(AMIS_FOR_11211, {
zip: '11211',
location: { zip: '11211' },
owner_status: 'homeowner',
household_income: 300000,
tax_filing: 'joint',
Expand All @@ -61,6 +61,7 @@ test('correctly evaluates scenerio "Joint w/ $300k Household income"', async t =

test('correctly evaluates scenerio "Single w/ $120k Household income in the Bronx"', async t => {
const data = calculateIncentives(AMIS_FOR_11211, {
location: { zip: '11211' },
owner_status: 'homeowner',
household_income: 120000,
tax_filing: 'single',
Expand All @@ -79,12 +80,18 @@ test('correctly evaluates scenerio "Single w/ $120k Household income in the Bron
t.equal(data.tax_credit_incentives.length, 10);

// count the incentives by key used to de-dupe in UI:
const rebateCounts = _.countBy(i => i.item + i.item_type);
const rebateCounts = _.countBy(
data.pos_rebate_incentives,
i => i.item + i.item_type,
);
t.equal(
Object.values(rebateCounts).every(c => c === 1),
true,
);
const taxCreditCounts = _.countBy(i => i.item + i.item_type);
const taxCreditCounts = _.countBy(
data.tax_credit_incentives,
i => i.item + i.item_type,
);
t.equal(
Object.values(taxCreditCounts).every(c => c === 1),
true,
Expand Down Expand Up @@ -153,6 +160,7 @@ test('correctly evaluates scenerio "Single w/ $120k Household income in the Bron

test('correctly evaluates scenerio "Married filing jointly w/ 2 kids and $250k Household income in San Francisco"', async t => {
const data = calculateIncentives(AMIS_FOR_94117, {
location: { zip: '94117' },
owner_status: 'homeowner',
household_income: 250000,
tax_filing: 'joint',
Expand All @@ -171,12 +179,18 @@ test('correctly evaluates scenerio "Married filing jointly w/ 2 kids and $250k H
t.equal(data.tax_credit_incentives.length, 10);

// count the incentives by key used to de-dupe in UI:
const rebateCounts = _.countBy(i => i.item + i.item_type);
const rebateCounts = _.countBy(
data.pos_rebate_incentives,
i => i.item + i.item_type,
);
t.equal(
Object.values(rebateCounts).every(c => c === 1),
true,
);
const taxCreditCounts = _.countBy(i => i.item + i.item_type);
const taxCreditCounts = _.countBy(
data.tax_credit_incentives,
i => i.item + i.item_type,
);
t.equal(
Object.values(taxCreditCounts).every(c => c === 1),
true,
Expand Down Expand Up @@ -245,6 +259,7 @@ test('correctly evaluates scenerio "Married filing jointly w/ 2 kids and $250k H

test('correctly evaluates scenerio "Hoh w/ 6 kids and $500k Household income in Missisippi"', async t => {
const data = calculateIncentives(AMIS_FOR_39503, {
location: { zip: '39503' },
owner_status: 'homeowner',
household_income: 500000,
tax_filing: 'hoh',
Expand All @@ -263,12 +278,18 @@ test('correctly evaluates scenerio "Hoh w/ 6 kids and $500k Household income in
t.equal(data.tax_credit_incentives.length, 10);

// count the incentives by key used to de-dupe in UI:
const rebateCounts = _.countBy(i => i.item + i.item_type);
const rebateCounts = _.countBy(
data.pos_rebate_incentives,
i => i.item + i.item_type,
);
t.equal(
Object.values(rebateCounts).every(c => c === 1),
true,
);
const taxCreditCounts = _.countBy(i => i.item + i.item_type);
const taxCreditCounts = _.countBy(
data.tax_credit_incentives,
i => i.item + i.item_type,
);
t.equal(
Object.values(taxCreditCounts).every(c => c === 1),
true,
Expand Down Expand Up @@ -338,23 +359,23 @@ test('correctly evaluates scenerio "Hoh w/ 6 kids and $500k Household income in

test('correctly sorts incentives"', async t => {
const data = calculateIncentives(AMIS_FOR_11211, {
zip: '11211',
location: { zip: '11211' },
owner_status: 'homeowner',
household_income: 120000,
tax_filing: 'single',
household_size: 1,
});
for (let incentives of [
for (const incentives of [
data.pos_rebate_incentives,
data.tax_credit_incentives,
]) {
let prevIncentive = incentives[0];
incentives.slice(1).forEach(incentive => {
if (prevIncentive.amount_type === incentive.amount_type) {
if (prevIncentive.amount.type === incentive.amount.type) {
t.ok(prevIncentive.amount >= incentive.amount);
} else {
t.equal(prevIncentive.amount_type, 'percent');
t.equal(incentive.amount_type, 'dollar_amount');
t.equal(prevIncentive.amount.type, 'percent');
t.equal(incentive.amount.type, 'dollar_amount');
}
prevIncentive = incentive;
});
Expand Down
2 changes: 1 addition & 1 deletion test/mocks/geocoder.js → test/mocks/geocoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const LMI_GEOCODE_FIXTURE = JSON.parse(
);

class MockGeocoder {
geocode(address, fields) {
geocode(address: string, fields: string[]) {
if (
address === '4986 Zuni St, Denver, CO 80221' &&
fields.length === 1 &&
Expand Down
24 changes: 14 additions & 10 deletions test/routes/v0.test.js → test/routes/v0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { test, beforeEach } from 'tap';
import { build } from '../helper.js';
import Ajv from 'ajv';
import fs from 'fs';
import qs from 'qs';

// NOTE: path is relative to test command, not this file (apparently)
const incentiveSchema = JSON.parse(
Expand All @@ -15,10 +16,13 @@ beforeEach(() => {
process.setMaxListeners(100);
});

async function getCalculatorResponse(t, query) {
async function getCalculatorResponse(
t: Tap.Test,
query: Record<string, unknown>,
) {
const app = await build(t);

const searchParams = new URLSearchParams(query);
const searchParams = qs.stringify(query, { encodeValuesOnly: true });
const url = `/api/v0/calculator?${searchParams}`;

const res = await app.inject({ url });
Expand All @@ -38,15 +42,15 @@ test('response is valid and correct', async t => {

const calculatorResponse = JSON.parse(res.payload);

const ajv = new Ajv({
const ajv = new Ajv.default({
schemas: [incentiveSchema, responseSchema],
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
allErrors: false,
});

const responseValidator = ajv.getSchema('WebsiteCalculatorResponse');
const responseValidator = ajv.getSchema('WebsiteCalculatorResponse')!;

// validate the response is a WebsiteCalculatorResponse
await responseValidator(calculatorResponse);
Expand Down Expand Up @@ -227,7 +231,7 @@ const BAD_QUERIES = [
test('bad queries', async t => {
t.plan(BAD_QUERIES.length * 3, 'expect 3 assertions per bad query');

for (let query of BAD_QUERIES) {
for (const query of BAD_QUERIES) {
const res = await getCalculatorResponse(t, query);
const calculatorResponse = JSON.parse(res.payload);
t.equal(res.statusCode, 400, 'response status is 400');
Expand Down Expand Up @@ -306,12 +310,12 @@ const ESTIMATION_TESTS = [
},
1450,
],
];
] as const;

test('estimated savings', async t => {
t.plan(ESTIMATION_TESTS.length, 'expect 1 assertion per estimate');

for (let [query, expected_amount] of ESTIMATION_TESTS) {
for (const [query, expected_amount] of ESTIMATION_TESTS) {
const res = await getCalculatorResponse(t, query);
const calculatorResponse = JSON.parse(res.payload);
t.equal(calculatorResponse.estimated_annual_savings, expected_amount);
Expand All @@ -325,17 +329,17 @@ test('/incentives', async t => {
t.equal(incentivesResponse.incentives.length, 30);
t.equal(res.statusCode, 200, 'response status is 200');

const ajv = new Ajv({
const ajv = new Ajv.default({
schemas: [incentiveSchema],
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
allErrors: false,
});

const validator = ajv.getSchema('WebsiteIncentive');
const validator = ajv.getSchema('WebsiteIncentive')!;

for (var incentive of incentivesResponse.incentives) {
for (const incentive of incentivesResponse.incentives) {
await validator(incentive);
t.equal(validator.errors, null);
}
Expand Down
25 changes: 14 additions & 11 deletions test/routes/v1.test.js → test/routes/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ beforeEach(() => {
process.setMaxListeners(100);
});

async function getCalculatorResponse(t, query) {
async function getCalculatorResponse(
t: Tap.Test,
query: Record<string, unknown>,
) {
const app = await build(t);

const searchParams = qs.stringify(query, { encodeValuesOnly: true });
Expand All @@ -34,15 +37,15 @@ test('response is valid and correct', async t => {

const calculatorResponse = JSON.parse(res.payload);

const ajv = new Ajv({
const ajv = new Ajv.default({
schemas: [API_CALCULATOR_RESPONSE_SCHEMA],
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
allErrors: false,
});

const responseValidator = ajv.getSchema('APICalculatorResponse');
const responseValidator = ajv.getSchema('APICalculatorResponse')!;

// validate the response is an APICalculatorResponse
await responseValidator(calculatorResponse);
Expand Down Expand Up @@ -73,15 +76,15 @@ test('response with state and utility is valid and correct', async t => {

const calculatorResponse = JSON.parse(res.payload);

const ajv = new Ajv({
const ajv = new Ajv.default({
schemas: [API_CALCULATOR_RESPONSE_SCHEMA],
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
allErrors: false,
});

const responseValidator = ajv.getSchema('APICalculatorResponse');
const responseValidator = ajv.getSchema('APICalculatorResponse')!;

// validate the response is an APICalculatorResponse
await responseValidator(calculatorResponse);
Expand Down Expand Up @@ -243,7 +246,7 @@ const BAD_QUERIES = [
test('bad queries', async t => {
t.plan(BAD_QUERIES.length * 3, 'expect 3 assertions per bad query');

for (let query of BAD_QUERIES) {
for (const query of BAD_QUERIES) {
const res = await getCalculatorResponse(t, query);
const calculatorResponse = JSON.parse(res.payload);
t.equal(res.statusCode, 400, 'response status is 400');
Expand Down Expand Up @@ -277,17 +280,17 @@ test('/incentives', async t => {
t.equal(incentivesResponse.incentives.length, 30);
t.equal(res.statusCode, 200, 'response status is 200');

const ajv = new Ajv({
const ajv = new Ajv.default({
schemas: [{ ...API_INCENTIVE_SCHEMA, $id: 'APIIncentive' }],
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
allErrors: false,
});

const validator = ajv.getSchema('APIIncentive');
const validator = ajv.getSchema('APIIncentive')!;

for (var incentive of incentivesResponse.incentives) {
for (const incentive of incentivesResponse.incentives) {
await validator(incentive);
t.equal(validator.errors, null);
}
Expand All @@ -304,14 +307,14 @@ test('/utilities', async t => {

const utilitiesResponse = JSON.parse(res.payload);

const ajv = new Ajv({
const ajv = new Ajv.default({
schemas: [API_UTILITIES_RESPONSE_SCHEMA],
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
allErrors: false,
});
const validator = ajv.getSchema('APIUtilitiesResponse');
const validator = ajv.getSchema('APIUtilitiesResponse')!;
await validator(utilitiesResponse);
t.equal(validator.errors, null);

Expand Down

0 comments on commit 18f6cc4

Please sign in to comment.