Skip to content

Commit 3cc22b8

Browse files
committed
Add unittest to firebase cloud functions. Fix tsc node_modules resolution.
Ensure all auth_code guarded functions generate 401s on invalid auth_code. Do basic test of transcript reading/writing from fake datastore. Also fix issue where tsc in the functions directory would look up the parent node_modules for type resolution in ../common. microsoft/TypeScript#30124 (comment) This yielded all sorts of hilarity.
1 parent 6e8beba commit 3cc22b8

File tree

16 files changed

+315
-225
lines changed

16 files changed

+315
-225
lines changed

app/[category]/v/[prefix]/[videoId]/[lang]/page.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import * as FirebaseUtils from 'utilities/firebase'
12
import BoardMeeting from 'components/BoardMeeting'
23
import TranscriptControlProvider from 'components/TranscriptControlProvider'
3-
import { DiarizedTranscript } from "common/transcript"
44
import { CloudStorageAccessor } from "common/storage"
5+
import { DiarizedTranscript } from "common/transcript"
56
import { Metadata, ResolvingMetadata } from "next"
67
import { getMetadata } from "utilities/metadata-utils"
78
import { loadSpeakerControlInfo } from 'utilities/client/speaker'
@@ -18,7 +19,9 @@ type Props = {
1819
searchParams: { [key: string]: string | string[] | undefined }
1920
};
2021

21-
const cloudStorageAccessor = new CloudStorageAccessor();
22+
const cloudStorageAccessor = new CloudStorageAccessor({
23+
bucket: FirebaseUtils.storage.bucket(Constants.STORAGE_BUCKET)
24+
});
2225

2326
export async function generateMetadata(
2427
{ params, searchParams }: Props,

common/storage.ts

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import * as Constants from 'config/constants';
2-
import * as fs from 'node:fs';
3-
import { Storage } from '@google-cloud/storage';
41
import { Stream } from "stream";
52
import { arrayBuffer } from 'node:stream/consumers';
63
import { createGzip, createGunzip } from 'zlib';
@@ -21,7 +18,7 @@ export interface StorageAccessor {
2118
}
2219

2320
type CloudStorageAccessorOptions = {
24-
keyfile?: string;
21+
bucket: Bucket;
2522
makeLzmaDecompressor?: (() => object);
2623
makeLzmaCompressor?: (() => object);
2724
};
@@ -35,19 +32,12 @@ type CloudStorageAccessorOptions = {
3532
// files so adding the lzma library in is just bloat. This is slightly
3633
// ugly but it works out as an okay compromise.
3734
export class CloudStorageAccessor implements StorageAccessor {
38-
readonly storage : Storage;
3935
readonly bucket : Bucket;
4036
readonly makeLzmaDecompressor?: (() => object);
4137
readonly makeLzmaCompressor?: (() => object);
4238

43-
constructor(options : CloudStorageAccessorOptions = {}) {
44-
if (options.keyfile) {
45-
const serviceAccount = JSON.parse(fs.readFileSync(options.keyfile, {encoding: 'utf8'}));
46-
this.storage = new Storage(serviceAccount);
47-
} else {
48-
this.storage = new Storage();
49-
}
50-
this.bucket = this.storage.bucket(Constants.STORAGE_BUCKET);
39+
constructor(options : CloudStorageAccessorOptions) {
40+
this.bucket = options.bucket;
5141
this.makeLzmaDecompressor = options.makeLzmaDecompressor;
5242
this.makeLzmaCompressor = options.makeLzmaCompressor;
5343
}

functions/jest.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const config: Config = {
141141
// runner: "jest-runner",
142142

143143
// The paths to modules that run some code to configure or set up the testing environment before each test
144-
// setupFiles: [],
144+
setupFiles: [ '<rootDir>/jest.setup.js' ],
145145

146146
// A list of paths to modules that run some code to configure or set up the testing framework before each test
147147
// setupFilesAfterEnv: [],

functions/package-lock.json

+54-56
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

functions/package.json

+12-7
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
"type": "module",
44
"scripts": {
55
"lint": "eslint --ext .js,.ts .",
6-
"build": "esbuild src/index.ts --bundle --platform=node --outfile=lib/index.js --format=esm --external:./node_modules/* --packages=external",
7-
"dev:tsc": "tsc --watch --preserveWatchOutput",
8-
"dev:node": "firebase emulators:start --only functions",
9-
"dev:esbuild": "npm run build --watch",
10-
"dev": "run-p dev:*",
6+
"build": "run-s build:tsc build:esbuild",
7+
"build:esbuild": "esbuild src/index.ts --bundle --platform=node --outfile=lib/index.js --format=esm --external:./node_modules/* --packages=external",
8+
"build:tsc": "tsc",
9+
"build:watch": "run-p 'build:tsc -- --watch --preserveWatchOutput' 'build:esbuild -- --watch'",
10+
"dev": "run-p build:watch dev:*",
11+
"dev:emulators": "firebase emulators:start --only functions,storage,database",
1112
"serve": "npm run build && firebase emulators:start --only functions,storage,database",
1213
"shell": "npm run build && firebase functions:shell",
1314
"start": "npm run shell",
1415
"deploy": "firebase deploy --only functions",
15-
"test": "firebase emulators:exec --only storage,database 'npx jest'",
16-
"test:watch": "firebase emulators:exec --only storage,database 'npx jest --watch'",
16+
"test": "run-s build test:jest",
17+
"test:jest": "firebase emulators:exec --only functions,storage,database 'npx jest'",
18+
"test:jest:watch": "firebase emulators:exec --ui --only functions,storage,database 'npx jest --watch'",
19+
"test:watch": "run-p build:watch test:jest:watch",
1720
"logs": "firebase functions:log"
1821
},
1922
"main": "lib/index.js",
@@ -33,6 +36,8 @@
3336
"youtubei.js": "^9.4.0"
3437
},
3538
"devDependencies": {
39+
"@types/express": "^4.17.21",
40+
"@types/jest": "^29.5.12",
3641
"@typescript-eslint/eslint-plugin": "^5.12.0",
3742
"@typescript-eslint/parser": "^5.12.0",
3843
"esbuild": "^0.21.5",

functions/src/endpoint_auth.test.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as TestingUtils from 'utils/testing';
2+
3+
// List of endpoints that should be guarded by user_id/auth_code params.
4+
const AUTH_CODE_ENDPOINTS = [
5+
[ 'GET', 'video_queue' ],
6+
[ 'PATCH', 'video_queue' ],
7+
[ 'DELETE', 'video_queue' ],
8+
[ 'GET', 'video_queue' ],
9+
10+
[ 'DELETE', 'vast' ],
11+
12+
[ 'PUT', 'transcript' ],
13+
];
14+
15+
describe('auth_code endpoints', () => {
16+
beforeAll(TestingUtils.beforeAll);
17+
18+
for (const [method, endpoint] of AUTH_CODE_ENDPOINTS) {
19+
it(`${method} ${endpoint} needs user_id`, async () => {
20+
const response = await TestingUtils.fetchEndpoint(
21+
endpoint,
22+
method,
23+
{auth_code: TestingUtils.FAKE_AUTH_CODE});
24+
expect(response.status).toStrictEqual(401);
25+
const responseJson = await response.json();
26+
expect(responseJson.ok).toStrictEqual(false);
27+
expect(responseJson.message).toMatch(/missing user_id/);
28+
});
29+
30+
it(`${method} ${endpoint} needs auth_code`, async () => {
31+
const response = await TestingUtils.fetchEndpoint(
32+
endpoint,
33+
method,
34+
{ user_id: TestingUtils.FAKE_USER_ID });
35+
expect(response.status).toStrictEqual(401);
36+
const responseJson = await response.json();
37+
expect(responseJson.ok).toStrictEqual(false);
38+
expect(responseJson.message).toMatch(/invalid auth_code/);
39+
});
40+
41+
it(`${method} ${endpoint} rejects invalid auth_code`, async () => {
42+
const response = await TestingUtils.fetchEndpoint(
43+
endpoint,
44+
method,
45+
{
46+
user_id: TestingUtils.FAKE_USER_ID,
47+
auth_code: (TestingUtils.FAKE_AUTH_CODE + TestingUtils.FAKE_AUTH_CODE)
48+
});
49+
expect(response.status).toStrictEqual(401);
50+
const responseJson = await response.json();
51+
expect(responseJson.ok).toStrictEqual(false);
52+
expect(responseJson.message).toMatch(/invalid auth_code/);
53+
});
54+
}
55+
});
56+

functions/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import 'source-map-support/register.js';
22

33
export { speakerinfo } from "./speakerinfo";
4-
export { metadata, transcript } from "./transcript";
4+
export { transcript } from "./transcript";
55
export { video_queue, vast } from "./video_queue";
66

77
import { initializeFirebase } from "./utils/firebase";
88

9-
initializeFirebase({});
9+
initializeFirebase();

functions/src/migrations/2024-06-15-sentece-tables.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,14 @@
99
// /transcripts/public/[category]/sentences/[vid].eng.json
1010

1111
import * as Constants from 'config/constants';
12-
import process from 'node:process';
13-
import { CloudStorageAccessor } from 'common/storage';
12+
import { getLzmaStorageAccessor } from 'utils/storage';
1413
import { DiarizedTranscript, makeSentenceTablePath, makeTranscriptDataPath } from 'common/transcript';
1514
import { basename } from 'node:path';
1615
import { makePublicPath } from 'common/paths';
1716

18-
1917
import type { Iso6393Code, VideoId } from "common/params";
2018

21-
const accessor = new CloudStorageAccessor({keyfile:process.env.TRANSCRIPT_STORAGE_KEYFILE});
19+
const accessor = getLzmaStorageAccessor();
2220

2321
for (const category of Constants.ALL_CATEGORIES) {
2422
const [allFiles] = await accessor.bucket.getFiles(

0 commit comments

Comments
 (0)