Skip to content

Commit

Permalink
feat: detect api version
Browse files Browse the repository at this point in the history
closes #21
  • Loading branch information
Daniel J. Lauk committed Aug 16, 2021
1 parent bb41486 commit 95d7b4f
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/api/util.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { detectVersion } from './util'
import { fetchWithTimeout } from '../http'
jest.mock('../http')
const mockedFetch = fetchWithTimeout as jest.MockedFunction<
typeof fetchWithTimeout
>

const DEFAULT_URL = 'http://localhost:8080'

describe('module api/util', () => {
beforeEach(() => {
mockedFetch.mockClear()
})

describe('detectVersion', () => {
it('returns version string from version field in response', async () => {
const version = '1.2.3-rc4'
mockedFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve({ version }),
} as Response)
const actualVersion = await detectVersion(DEFAULT_URL)
expect(actualVersion).toBe(version)
})

it('returns undefined on HTTP 404', async () => {
mockedFetch.mockResolvedValueOnce({
ok: false,
status: 404,
statusText: 'not found',
} as Response)
const actualVersion = await detectVersion(DEFAULT_URL)
expect(actualVersion).toBe(undefined)
})

it('throws on other HTTP errors', async () => {
const fakeResponses = [
{ ok: false, status: 400, statusText: 'bad request' },
{ ok: false, status: 403, statusText: 'forbidden' },
{ ok: false, status: 405, statusText: 'method not allowed' },
{ ok: false, status: 406, statusText: 'not acceptable' },
{ ok: false, status: 408, statusText: 'request timeout' },
{ ok: false, status: 500, statusText: 'internal server error' },
{ ok: false, status: 501, statusText: 'not implemented' },
{ ok: false, status: 502, statusText: 'bad gateway' },
{ ok: false, status: 503, statusText: 'service unavailable' },
]
for (const r of fakeResponses) {
mockedFetch.mockResolvedValueOnce(r as Response)
await expect(detectVersion(DEFAULT_URL)).rejects.toThrow()
}
})

it('throws if URL is falsy', async () => {
const urls = [null, undefined, '']
expect.assertions(urls.length)
for (const u of urls) {
await expect(detectVersion(u as string)).rejects.toThrow()
}
})
})
})
40 changes: 40 additions & 0 deletions src/api/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as decoders from 'decoders'
import { fetchWithTimeout } from '../http'

type VersionInfo = {
version: string
}
const versionGuard = decoders.guard(
decoders.object({ version: decoders.string })
)

/**
* Contact the API provider at base URL `url` and try to read
* version information from endpoint `/version` with a GET request.
*
* @param url base url of the API provider
* @returns A Promise that resolves to either the version string, or undefined if the version could not be read.
*/
export const detectVersion = async (
url: RequestInfo
): Promise<string | undefined> => {
if (!url) throw new Error('need url')
const resp = await fetchWithTimeout(url + '/version', {
method: 'GET',
headers: { Accept: 'application/json' },
})
if (resp.status === 404) {
return undefined
}
if (!resp.ok) {
throw new Error(
`could not detect version: ${resp.status} ${resp.statusText}`
)
}
try {
const versionInfo: VersionInfo = versionGuard(await resp.json())
return versionInfo.version
} catch (e) {
throw new Error('received malformed version info')
}
}

0 comments on commit 95d7b4f

Please sign in to comment.