Skip to content

Commit 718b0c6

Browse files
committed
Adds better testing
1 parent 92640d8 commit 718b0c6

File tree

15 files changed

+230
-73
lines changed

15 files changed

+230
-73
lines changed

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
lint:
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 5
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Install dependencies
18+
run: npm install
19+
- name: Lint
20+
run: npm run lint
21+
22+
test:
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 5
25+
steps:
26+
- uses: actions/checkout@v4
27+
- name: Install dependencies
28+
run: npm install
29+
- name: Test
30+
run: npm test

.github/workflows/deploy.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ on:
55
branches:
66
- main
77
repository_dispatch:
8+
89
jobs:
910
deploy:
1011
runs-on: ubuntu-latest
1112
name: Deploy
1213
steps:
13-
- uses: actions/checkout@v4
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
1417
- name: Publish
1518
uses: cloudflare/wrangler-action@v3
1619
with:

src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const PLATFORM_FILTERS = {
3030
},
3131
[AVAILABLE_PLATFORMS.Windows]: {
3232
extension: 'zip',
33-
matches: ['x64', 'x32'],
33+
matches: ['x86', 'x64', 'x32'],
3434
},
3535
[AVAILABLE_PLATFORMS.Linux]: {
3636
extension: 'gz',

src/getPlatform.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { AVAILABLE_PLATFORMS, AVAILABLE_ARCHITECTURES } from './constants'
2+
import { testAsset } from './getPlatform'
3+
import { describe, it, expect } from 'vitest'
4+
5+
describe('testAsset', () => {
6+
it('returns true for matching platform, architecture, and file extension', () => {
7+
const target = AVAILABLE_PLATFORMS.Windows
8+
const arch = AVAILABLE_ARCHITECTURES.x86
9+
const fileName = 'example-x86.zip'
10+
11+
const result = testAsset(target, arch, fileName)
12+
13+
expect(result).toBe(true)
14+
})
15+
16+
it('returns false for non-matching platform', () => {
17+
const target = AVAILABLE_PLATFORMS.Linux
18+
const arch = AVAILABLE_ARCHITECTURES.x86
19+
const fileName = 'example-x86.zip'
20+
21+
const result = testAsset(target, arch, fileName)
22+
23+
expect(result).toBe(false)
24+
})
25+
26+
it('returns false for non-matching architecture', () => {
27+
const target = AVAILABLE_PLATFORMS.Windows
28+
const arch = AVAILABLE_ARCHITECTURES.arm64
29+
const fileName = 'example-x86.zip'
30+
31+
const result = testAsset(target, arch, fileName)
32+
33+
expect(result).toBe(false)
34+
})
35+
36+
it('returns false for non-matching file extension', () => {
37+
const target = AVAILABLE_PLATFORMS.Windows
38+
const arch = AVAILABLE_ARCHITECTURES.x86
39+
const fileName = 'example-x86.gz'
40+
41+
const result = testAsset(target, arch, fileName)
42+
43+
expect(result).toBe(false)
44+
})
45+
})

src/getPlatform.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,24 @@ import {
66
} from './constants'
77
import { fileExt } from './utils/fileExt'
88

9-
export const testAsset = (
9+
export function testAsset(
1010
target: AVAILABLE_PLATFORMS,
1111
arch: AVAILABLE_ARCHITECTURES,
1212
fileName: string,
13-
): boolean => {
13+
): boolean {
1414
const { matches, extension } = PLATFORM_FILTERS[target]
1515
const arch_matches = ARCH_FILTERS[arch]
1616
const rightArch =
1717
arch_matches && ARCH_FILTERS[arch].some((arch) => fileName.includes(arch))
1818

1919
// .app gz files don't have arch in the name
2020
if (!rightArch && target !== AVAILABLE_PLATFORMS.MacOS) {
21+
console.error(`File ${fileName} has wrong architecture`)
2122
return false
2223
}
2324

2425
if (fileExt(fileName) !== extension) {
26+
console.error(`File ${fileName} has wrong extension`)
2527
return false
2628
}
2729

src/handler.ts

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { findAssetSignature, getLatestRelease } from './services/github'
77
import { TauriUpdateResponse } from './types'
88
import { sanitizeVersion } from './utils/versioning'
99

10-
import { Request, ExecutionContext } from '@cloudflare/workers-types';
11-
import { WritableStream as WebWritableStream } from 'node:stream/web';
12-
import { Env } from '../worker-configuration';
10+
import { Request, ExecutionContext } from '@cloudflare/workers-types'
11+
import { WritableStream as WebWritableStream } from 'node:stream/web'
12+
import { Env } from '../worker-configuration'
1313

1414
declare global {
1515
const GITHUB_ACCOUNT: string
@@ -26,10 +26,8 @@ const SendJSON = (data: Record<string, unknown>) => {
2626
const responses = {
2727
NotFound: () => new Response('Not found', { status: 404 }),
2828
NoContent: () => new Response(null, { status: 204 }),
29-
SendUpdate: (data: TauriUpdateResponse) =>
30-
SendJSON(data),
31-
SendJSON
32-
29+
SendUpdate: (data: TauriUpdateResponse) => SendJSON(data),
30+
SendJSON,
3331
}
3432

3533
type RequestPathParts = [
@@ -38,7 +36,11 @@ type RequestPathParts = [
3836
AVAILABLE_ARCHITECTURES,
3937
string,
4038
]
41-
const handleV1Request = async (request: Request, env: Env, ctx: ExecutionContext) => {
39+
const handleV1Request = async (
40+
request: Request,
41+
env: Env,
42+
ctx: ExecutionContext,
43+
) => {
4244
const path = new URL(request.url).pathname
4345
const [, target, arch, appVersion] = path
4446
.slice(1)
@@ -70,8 +72,10 @@ const handleV1Request = async (request: Request, env: Env, ctx: ExecutionContext
7072
}
7173

7274
const signature = await findAssetSignature(match.name, release.assets)
73-
const proxy = GITHUB_TOKEN?.length;
74-
const downloadURL = proxy ? createProxiedFileUrl(request, env, ctx, match.browser_download_url) : match.browser_download_url
75+
const proxy = GITHUB_TOKEN?.length
76+
const downloadURL = proxy
77+
? createProxiedFileUrl(request, env, ctx, match.browser_download_url)
78+
: match.browser_download_url
7579
const data: TauriUpdateResponse = {
7680
url: downloadURL,
7781
version: remoteVersion,
@@ -83,47 +87,65 @@ const handleV1Request = async (request: Request, env: Env, ctx: ExecutionContext
8387
return responses.SendUpdate(data)
8488
}
8589

86-
const createProxiedFileUrl = (request: Request, env: Env, ctx: ExecutionContext, downloadURL: string) => {
87-
90+
const createProxiedFileUrl = (
91+
request: Request,
92+
env: Env,
93+
ctx: ExecutionContext,
94+
downloadURL: string,
95+
) => {
8896
const fileName = downloadURL.split('/')?.at(-1)
89-
if (!fileName) { throw new Error('Could not get file name from download URL') }
90-
97+
if (!fileName) {
98+
throw new Error('Could not get file name from download URL')
99+
}
91100

92101
const path = new URL(request.url)
93102
const root = `${path.protocol}//${path.host}`
94103

95104
return new URL(`/latest/${fileName}`, root).toString()
96105
}
97106

98-
const getLatestAssets = async (request: Request, env: Env, ctx: ExecutionContext) => {
107+
const getLatestAssets = async (
108+
request: Request,
109+
env: Env,
110+
ctx: ExecutionContext,
111+
) => {
99112
const fileName = request.url.split('/')?.at(-1)
100-
if (!fileName) { throw new Error('Could not get file name from download URL') }
113+
if (!fileName) {
114+
throw new Error('Could not get file name from download URL')
115+
}
101116

102117
const release = await getLatestRelease(request, env, ctx)
103-
const downloadPath = release.assets.find(({ name }) => name === fileName)?.browser_download_url
118+
const downloadPath = release.assets.find(
119+
({ name }) => name === fileName,
120+
)?.browser_download_url
104121

105-
if (!downloadPath) { throw new Error('Could not get file path from download URL') }
122+
if (!downloadPath) {
123+
throw new Error('Could not get file path from download URL')
124+
}
106125

107-
const { readable } = new TransformStream<Uint8Array, Uint8Array>();
126+
const { readable } = new TransformStream<Uint8Array, Uint8Array>()
108127
const file_response = await fetch(downloadPath, {
109128
method: 'GET',
110-
redirect: 'follow'
129+
redirect: 'follow',
111130
})
112131

113132
if (file_response.body) {
114-
const webWritableStream = new WebWritableStream();
115-
await file_response.body.pipeTo(webWritableStream);
116-
return new Response(readable, file_response);
133+
const webWritableStream = new WebWritableStream()
134+
await file_response.body.pipeTo(webWritableStream)
135+
return new Response(readable, file_response)
117136
}
118137

119138
throw new Error('Could not get file body from download URL')
120139
}
121140

122141
// eslint-disable-next-line @typescript-eslint/no-unused-vars
123-
export async function handleRequest(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
142+
export async function handleRequest(
143+
request: Request,
144+
env: Env,
145+
ctx: ExecutionContext,
146+
): Promise<Response> {
124147
const path = new URL(request.url).pathname
125148

126-
127149
if (path.includes('/latest')) {
128150
return getLatestAssets(request, env, ctx)
129151
}

src/index.test.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
2-
import { describe, it, expect } from "vitest";
3-
import { Env } from '../worker-configuration';
1+
import { createExecutionContext, waitOnExecutionContext } from 'cloudflare:test'
2+
import { describe, it, expect } from 'vitest'
3+
import { Env } from '../worker-configuration'
44
// Could import any other source file/function here
5-
import worker from "../src";
5+
import worker from '../src'
66

77
// For now, you'll need to do something like this to get a correctly-typed
88
// `Request` to pass to `worker.fetch()`.
9-
const IncomingRequest = Request<unknown, IncomingRequestCfProperties>;
9+
const IncomingRequest = Request<unknown, IncomingRequestCfProperties>
1010

11-
describe("handle GET", () => {
12-
it("responds with 200", async () => {
13-
const request = new IncomingRequest("http://example.com");
14-
// Create an empty context to pass to `worker.fetch()`
15-
const ctx = createExecutionContext();
16-
const env: Env = {
17-
GITHUB_ACCOUNT: 'killeencode',
18-
GITHUB_REPO: 'brancato'
19-
};
20-
const response = await worker.fetch(request, env, ctx);
21-
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
22-
await waitOnExecutionContext(ctx);
23-
expect(response.status).toBe(200);
24-
});
25-
});
11+
describe('handle GET', () => {
12+
it('responds with 200', async () => {
13+
const request = new IncomingRequest('http://example.com')
14+
// Create an empty context to pass to `worker.fetch()`
15+
const ctx = createExecutionContext()
16+
const env: Env = {
17+
GITHUB_ACCOUNT: 'killeencode',
18+
GITHUB_REPO: 'brancato',
19+
}
20+
const response = await worker.fetch(request, env, ctx)
21+
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
22+
await waitOnExecutionContext(ctx)
23+
expect(response.status).toBe(200)
24+
})
25+
})

src/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { handleRequest } from './handler';
2-
import { Request, ExecutionContext } from '@cloudflare/workers-types';
3-
import { Env } from '../worker-configuration';
1+
import { handleRequest } from './handler'
2+
import { Request, ExecutionContext } from '@cloudflare/workers-types'
3+
import { Env } from '../worker-configuration'
44

55
export default {
66
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
7-
return handleRequest(request, env, ctx);
7+
return handleRequest(request, env, ctx)
88
},
9-
};
9+
}

src/legacy/handler.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import { Asset, findAssetSignature, getReleases } from '../services/github'
44
import { TauriUpdateResponse } from '../types'
55
import { sanitizeVersion, semverGt, semverValid } from '../utils/versioning'
66

7-
import { Env } from '../../worker-configuration';
8-
import { Request, ExecutionContext } from '@cloudflare/workers-types';
9-
10-
export async function handleLegacyRequest(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
7+
import { Env } from '../../worker-configuration'
8+
import { Request, ExecutionContext } from '@cloudflare/workers-types'
9+
10+
export async function handleLegacyRequest(
11+
request: Request,
12+
env: Env,
13+
ctx: ExecutionContext,
14+
): Promise<Response> {
1115
const path = new URL(request.url).pathname
1216
const [platform, version] = path.slice(1).split('/')
1317

src/services/github.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { Env } from '../../worker-configuration';
2-
import { Request, Response, ExecutionContext } from '@cloudflare/workers-types';
1+
import { Env } from '../../worker-configuration'
2+
import { Request, Response } from '@cloudflare/workers-types'
33

44
export type Asset = { name: string; browser_download_url: string }
55

6-
export const getReleases = async (request: Request, env: Env, ctx: ExecutionContext): Promise<Response> => {
6+
export const getReleases = async (
7+
request: Request,
8+
env: Env,
9+
): Promise<Response> => {
710
const reqUrl = new URL(
811
`https://api.github.com/repos/${env.GITHUB_ACCOUNT}/${env.GITHUB_REPO}/releases/latest`,
912
)
@@ -12,14 +15,15 @@ export const getReleases = async (request: Request, env: Env, ctx: ExecutionCont
1215
'User-Agent': request.headers.get('User-Agent') as string,
1316
})
1417

15-
if (env.GITHUB_TOKEN?.length) headers.set('Authorization', `token ${env.GITHUB_TOKEN}`)
18+
if (env.GITHUB_TOKEN?.length)
19+
headers.set('Authorization', `token ${env.GITHUB_TOKEN}`)
1620

1721
const response = await fetch(reqUrl.toString(), {
1822
method: 'GET',
1923
headers,
20-
});
24+
})
2125

22-
return response;
26+
return response
2327
}
2428

2529
type Release = {
@@ -29,8 +33,11 @@ type Release = {
2933
published_at: string
3034
}
3135

32-
export const getLatestRelease = async (request: Request, env: Env, ctx: ExecutionContext): Promise<Release> => {
33-
const releases = await getReleases(request, env, ctx)
36+
export const getLatestRelease = async (
37+
request: Request,
38+
env: Env,
39+
): Promise<Release> => {
40+
const releases = await getReleases(request, env)
3441

3542
return (await releases.json()) as Release
3643
}

0 commit comments

Comments
 (0)