Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial test around stampy.ts #276

Merged
merged 57 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e761b3e
feat(deps) add msw
jrhender Mar 23, 2023
7f4f560
test: initial msw setup
jrhender Mar 26, 2023
92722eb
test(jest config): add alias for ~ to match remix
jrhender May 1, 2023
c979141
test(stampy.ts): mock coda for question 2400
jrhender May 1, 2023
7cfefa8
chore(launch.json): add remix debug
jrhender May 26, 2023
2a3004b
feat(deps): install jest-environment-miniflare
jrhender Jun 4, 2023
67f2ee7
test: setup tsconfig and jest for miniflare
jrhender Jun 4, 2023
fddf03b
fix(tsconfig): ES2020 needed for string marchAll
jrhender Jun 4, 2023
d8db37a
test: remove msw mocks as using undici instead
jrhender Jun 4, 2023
4209c64
build: separate dev build from start script
jrhender Jun 18, 2023
09bd5c8
test(stampy.spec): use RequestForReload
jrhender Jun 19, 2023
775bcec
test(stampy.spec): add a couple useful assertions
jrhender Jun 20, 2023
1e8b60a
build(pr-check): build & wrangler for miniflare
jrhender Jun 21, 2023
2e92524
feat: add scripts with tsconfig
jrhender Jul 2, 2023
81c90ef
test: add npm script to run refresh mock data script
jrhender Jul 2, 2023
78cd07a
test: script query to coda
jrhender Jul 3, 2023
70b465d
test(stampy.spec): parameterize the test
jrhender Jul 4, 2023
efe00a6
test: use questionIds from shared question-list
jrhender Jul 6, 2023
1a80624
test(refresh-mock-return-data): use questionId in filename
jrhender Jul 6, 2023
8e0ede0
test(mock-data-script): read token from wrangler
jrhender Jul 9, 2023
1b0c411
test: revert the questionList approach
jrhender Jul 9, 2023
2642522
test(refresh-mock-data): promisify writefile
jrhender Jul 9, 2023
73278fb
chore: uninstall tsc-alias
jrhender Jul 9, 2023
0a59962
chore: undo package-lock.json for merge
jrhender Jul 9, 2023
0c6c92a
Merge branch 'master' into miniflare-jest-env
jrhender Jul 9, 2023
598c9e4
build(package-lock): update after merge
jrhender Jul 9, 2023
4740779
chore(package.json): rename generate to refresh
jrhender Jul 9, 2023
571bca5
test: improve mock dir name
jrhender Jul 9, 2023
175582a
docs(README): add note about refresh-test-data
jrhender Jul 9, 2023
2c60ffe
chore: use ts-node instead of tsc for script
jrhender Jul 9, 2023
105e5aa
test(refresh-coda-data): use fetch
jrhender Jul 18, 2023
26ba24c
Merge branch 'master' into miniflare-jest-env
jrhender Jul 18, 2023
5318403
tests(stampy): use AnswersRow
jrhender Jul 18, 2023
040edbd
build(ci): use template wrangler instead of dedicated ci
jrhender Jul 27, 2023
bf0995b
Merge branch 'master' into miniflare-jest-env
jrhender Jul 28, 2023
54304f4
test(stampy.spec): use string for questionId
jrhender Jul 28, 2023
e7cf3bb
test(stampy.ts): reuse coda url building
jrhender Jul 30, 2023
0c539de
chore: removing buildCodaBaseUrl
jrhender Jul 30, 2023
82068ca
feat(package.json): fixing commands in package.json
jrhender Aug 12, 2023
a5d2e36
test(stampy.ts): use questionId 0
jrhender Aug 12, 2023
7e35416
Merge branch 'master' into miniflare-jest-env
jrhender Aug 12, 2023
221c3f8
Merge branch 'master' into miniflare-jest-env
jrhender Aug 27, 2023
2940a34
test(refresh-coda-data): get array of coda queries
jrhender Aug 28, 2023
0fe95ab
test(stampy.spec): remove type workarounds
jrhender Sep 2, 2023
a4c8a7f
test(stampy.spec): improve mock method naming
jrhender Sep 2, 2023
549e506
Merge branch 'master' into miniflare-jest-env
jrhender Sep 16, 2023
dadecbf
Merge branch 'master' into miniflare-jest-env
jrhender Sep 19, 2023
ffe03cc
test(refresh-coda-data): also coda token get from env var
jrhender Sep 19, 2023
f10e292
ci(pr-check): refresh-test-data
jrhender Sep 19, 2023
45c5d95
docs(README): prettier format
jrhender Sep 19, 2023
3857fe6
test(refresh-coda-data): cache codaToken
jrhender Sep 19, 2023
4e87d16
test(refresh-coda-data): apply token with partial
jrhender Sep 20, 2023
44083a2
chore: refresh cached data
jrhender Sep 20, 2023
d50f99f
chore: undo launch.json changes
jrhender Sep 20, 2023
786416f
chore: prettier fix
jrhender Sep 20, 2023
dae626d
Merge branch 'master' into miniflare-jest-env
jrhender Oct 6, 2023
30845a9
chore(cached data): update cached coda response
jrhender Oct 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ on:
jobs:
pr-check:
runs-on: ubuntu-latest
env:
CODA_TOKEN: ${{ secrets.CODA_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: lint
run: |
npm ci
npm run lint
npm run build
cp wrangler.toml.template wrangler.toml
npm run refresh-test-data
npm run test
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ $ npm run lint
$ npm run test
```

Tests may rely on cached data from coda.
This data can be refreshed by running:

```sh
$ npm run refresh-test-data
```

## Deployment to your CF Worker

```sh
Expand Down
117,380 changes: 117,380 additions & 0 deletions app/mocks/coda-responses/cached-coda-responses.json

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions app/mocks/coda-responses/refresh-coda-data-for-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* This is a script to refresh the mock return data for tests.
*/

import * as fs from 'fs'
import _ from 'lodash'
import * as path from 'path'
import * as toml from 'toml'
import {
ALL_ANSWERS_TABLE,
BANNERS_TABLE,
QUESTION_DETAILS_TABLE,
TAGS_TABLE,
makeCodaRequest,
} from '../../server-utils/coda-urls'

async function main(): Promise<void> {
const codaRequestsToCache: CodaRequestParams[] = [
{
table: QUESTION_DETAILS_TABLE,
queryColumn: 'UI ID',
queryValue: '0',
},
{
table: ALL_ANSWERS_TABLE,
},
{
table: TAGS_TABLE,
queryColumn: 'Internal?',
queryValue: 'false',
},
{
table: BANNERS_TABLE,
},
]

const cacheResults: CachedCodaQueries = await Promise.all(
codaRequestsToCache.map(async (codaParams) => {
const cachedRequests = await getCodaData(codaParams)
return {codaParams, cachedRequests}
})
)
const writeData = JSON.stringify(cacheResults, null, 2)
const filename = `cached-coda-responses.json`
await writeFile(writeData, filename)
}

export type CachedCodaQueries = Array<CachedCodaQuery>
type CachedCodaQuery = {
codaParams: CodaRequestParams
cachedRequests: CachedRequest[]
}
type CodaRequestParams = {
table: string
queryColumn?: string
queryValue?: string
}
type CachedRequest = {
url: string
httpMethod: string
responseData: any
}

const getCodaData = async (codaRequestParams: CodaRequestParams) => {
const codaUrl = makeCodaRequest(codaRequestParams)
console.log(`Fetching ${codaUrl}`)
const cachedRequests: CachedRequest[] = []
await paginatedGet(codaUrl, cachedRequests)
return cachedRequests
}

const paginatedGet = async (url: string, responses: any[]) => {
const responseData = await getData(url)
const data = {url, httpMethod: 'GET', responseData}
responses.push(data)

if (responseData.nextPageLink) {
await paginatedGet(responseData.nextPageLink, responses)
}
return
}

const readCodaToken = (): string => {
const tokenFromEnv = process.env.CODA_TOKEN
if (tokenFromEnv) {
console.log('found Coda token from environment')
return tokenFromEnv
}
const wranglerToml = fs.readFileSync('wrangler.toml', 'utf8')
const config = toml.parse(wranglerToml)
const tokenFromToml: string = config.vars.CODA_TOKEN
if (tokenFromToml) {
console.log('found Coda token from wrangler config')
return tokenFromToml
}

throw Error('unable to get a Coda token')
}

const getDataWithAuthz = async (url: string, authzToken: string) => {
const options = {
headers: {
Authorization: `Bearer ${authzToken}`,
},
}
const response = await fetch(url, options)
return await response.json()
}

const getData = _.partialRight(getDataWithAuthz, readCodaToken())

const writeFile = (data: string, filename: string): Promise<void> => {
const filePath = path.join(__dirname, filename)

return new Promise((resolve, reject) => {
fs.writeFile(filePath, data, (err) => {
if (err) {
console.error('An error occurred:', err)
reject(err)
} else {
console.log('File has been written successfully.')
resolve()
}
})
})
}

main()
36 changes: 36 additions & 0 deletions app/server-utils/coda-urls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* This file contains the URLs for the Coda API.
* It is a separate file in part so usage doesn't require CF worker env vars.
*/

// Use table ide, rather than names, in case of renames
export const QUESTION_DETAILS_TABLE = 'grid-sync-1059-File' // Answers
export const INITIAL_QUESTIONS_TABLE = 'table-yWog6qRzV4' // Initial questions
export const ON_SITE_TABLE = 'table-aOTSHIz_mN' // On-site answers
export const ALL_ANSWERS_TABLE = 'table-YvPEyAXl8a' // All answers
export const INCOMING_QUESTIONS_TABLE = 'grid-S_6SYj6Tjm' // Incoming questions
export const TAGS_TABLE = 'grid-4uOTjz1Rkz'
export const WRITES_TABLE = 'table-eEhx2YPsBE'
export const GLOSSARY_TABLE = 'grid-_pSzs23jmw'
export const BANNERS_TABLE = 'grid-3WgZ9_NkvO'

type CodaRequest = {
table: string
queryColumn?: string
queryValue?: string
limit?: number
}
export const makeCodaRequest = ({table, queryColumn, queryValue, limit}: CodaRequest): string => {
let params = `useColumnNames=true&sortBy=natural&valueFormat=rich${
queryColumn && queryValue ? `&query=${quote(queryColumn)}:${quote(queryValue)}` : ''
}`
if (limit) {
params = `${params}&limit=${limit}`
}
return `https://coda.io/apis/v1/docs/${CODA_DOC_ID}/tables/${enc(table)}/rows?${params}`
}

export const CODA_DOC_ID = 'fau7sl2hmG'

const enc = encodeURIComponent
const quote = (x: string) => encodeURIComponent(`"${x.replace(/"/g, '\\"')}"`)
54 changes: 54 additions & 0 deletions app/server-utils/stampy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {loadQuestionDetail} from '~/server-utils/stampy'
import cachedCodaQueriesJson from '~/mocks/coda-responses/cached-coda-responses.json'
import {QUESTION_DETAILS_TABLE} from './coda-urls'
import {CachedCodaQueries} from '~/mocks/coda-responses/refresh-coda-data-for-tests'
import _ from 'lodash'

export const questions: Array<string> = ['0']
const cachedQueries = cachedCodaQueriesJson as CachedCodaQueries

describe('loadQuestionDetail', () => {
it.each<string>(questions)('can load question %i', async (questionId) => {
const fetchMock = getMiniflareFetchMock()
fetchMock.disableNetConnect()
const mockCodaRequest = _.partial(mockCodaRequestUsingMiniflareFetchMock, fetchMock)
const allCachedRequests = cachedQueries.flatMap((codaQuery) => codaQuery.cachedRequests)
for (const cachedRequest of allCachedRequests) {
mockCodaRequest(cachedRequest.url, cachedRequest.responseData)
}

const cachedQuery = cachedQueries.find((cachedQuery) =>
_.isEqual(cachedQuery.codaParams, {
table: QUESTION_DETAILS_TABLE,
queryColumn: 'UI ID',
queryValue: questionId,
})
)
if (!cachedQuery) {
throw new Error(`Cached response not found for question ${questionId}`)
}

const questionDetail = await loadQuestionDetail('NEVER_RELOAD', questionId)

const cachedQuestionData = cachedQuery.cachedRequests[0].responseData
const firstItem = cachedQuestionData.items[0]
expect(questionDetail.data.status).toBe(firstItem.values.Status.name)
const linkUrl = new URL(firstItem.values.Link.url)
expect(questionDetail.data.answerEditLink).toBe(linkUrl.origin + linkUrl.pathname)
jrhender marked this conversation as resolved.
Show resolved Hide resolved
})
})

const mockCodaRequestUsingMiniflareFetchMock = async (
fetchMock: ReturnType<typeof getMiniflareFetchMock>,
urlString: string,
responseData: any
) => {
const url = new URL(urlString)
const origin = fetchMock.get(`${url.origin}`)
origin
.intercept({
method: 'GET',
path: url.href.replace(url.origin, ''),
})
.reply(200, responseData)
}
47 changes: 15 additions & 32 deletions app/server-utils/stampy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ import {
urlToIframe,
convertToHtmlAndWrapInDetails,
} from '~/server-utils/parsing-utils'
import {
ALL_ANSWERS_TABLE,
CODA_DOC_ID,
GLOSSARY_TABLE,
INCOMING_QUESTIONS_TABLE,
INITIAL_QUESTIONS_TABLE,
ON_SITE_TABLE,
QUESTION_DETAILS_TABLE,
TAGS_TABLE,
WRITES_TABLE,
BANNERS_TABLE,
makeCodaRequest,
} from './coda-urls'

export enum QuestionState {
OPEN = '_',
Expand Down Expand Up @@ -88,7 +101,7 @@ type CodaRowCommon = {
browserLink: string
}

type AnswersRow = CodaRowCommon & {
export type AnswersRow = CodaRowCommon & {
values: {
'Edit Answer': string
Link: {
Expand Down Expand Up @@ -132,26 +145,12 @@ type BannersRow = CodaRowCommon & {
}
}
type CodaRow = AnswersRow | TagsRow | GlossaryRow | BannersRow
type CodaResponse = {
export type CodaResponse = {
items: CodaRow[]
nextPageLink: string | null
}

const CODA_DOC_ID = 'fau7sl2hmG'

// Use table ide, rather than names, in case of renames
const QUESTION_DETAILS_TABLE = 'grid-sync-1059-File' // Answers
const INITIAL_QUESTIONS_TABLE = 'table-yWog6qRzV4' // Initial questions
const ON_SITE_TABLE = 'table-aOTSHIz_mN' // On-site answers
const ALL_ANSWERS_TABLE = 'table-YvPEyAXl8a' // All answers
const INCOMING_QUESTIONS_TABLE = 'grid-S_6SYj6Tjm' // Incoming questions
const TAGS_TABLE = 'grid-4uOTjz1Rkz'
const WRITES_TABLE = 'table-eEhx2YPsBE'
const GLOSSARY_TABLE = 'grid-_pSzs23jmw'
const BANNERS_TABLE = 'grid-3WgZ9_NkvO'

const enc = encodeURIComponent
const quote = (x: string) => encodeURIComponent(`"${x.replace(/"/g, '\\"')}"`)
let allTags = {} as Record<string, Tag>
let allBanners = {} as Record<string, Banner>

Expand Down Expand Up @@ -213,22 +212,6 @@ const paginateCoda = async (url: string): Promise<CodaRow[]> => {
return items
}

type CodaRequest = {
table: string
queryColumn?: string
queryValue?: string
limit?: number
}
const makeCodaRequest = ({table, queryColumn, queryValue, limit}: CodaRequest): string => {
let params = `useColumnNames=true&sortBy=natural&valueFormat=rich${
queryColumn && queryValue ? `&query=${quote(queryColumn)}:${quote(queryValue)}` : ''
}`
if (limit) {
params = `${params}&limit=${limit}`
}
return `https://coda.io/apis/v1/docs/${CODA_DOC_ID}/tables/${enc(table)}/rows?${params}`
}

const getCodaRows = async (
table: string,
queryColumn?: string,
Expand Down
5 changes: 4 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testEnvironment: 'miniflare',
moduleNameMapper: {
'^~/(.*)$': '<rootDir>/app/$1',
},
}
Loading
Loading