Skip to content

Commit 05fd26b

Browse files
committed
refactor: add initial refactor work
1 parent fa98a7f commit 05fd26b

File tree

12 files changed

+1151
-111
lines changed

12 files changed

+1151
-111
lines changed

integration_test/package-lock.json

Lines changed: 519 additions & 61 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration_test/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
"@types/jest": "^29.5.11",
2121
"@types/js-yaml": "^4.0.9",
2222
"@types/node-fetch": "^2.6.11",
23+
"chalk": "^5.5.0",
24+
"dotenv": "^17.2.1",
2325
"jest": "^29.7.0",
2426
"p-limit": "^6.2.0",
2527
"p-retry": "^6.2.1",
26-
"ts-jest": "^29.1.1"
28+
"ts-jest": "^29.1.1",
29+
"zod": "^4.0.17"
2730
}
2831
}

integration_test/run.ts

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import client from "firebase-tools";
66
import { getRuntimeDelegate } from "firebase-tools/lib/deploy/functions/runtimes/index.js";
77
import { detectFromPort } from "firebase-tools/lib/deploy/functions/runtimes/discovery/index.js";
88
import setup from "./setup.js";
9-
import { loadEnv } from "./utils.js";
9+
import * as dotenv from "dotenv";
1010
import { deployFunctionsWithRetry, postCleanup } from "./deployment-utils.js";
1111

12-
loadEnv();
12+
dotenv.config();
1313

1414
let {
1515
DEBUG,
@@ -49,21 +49,26 @@ if (!["node", "python"].includes(TEST_RUNTIME)) {
4949
process.exit(1);
5050
}
5151

52-
if (!FIREBASE_ADMIN && TEST_RUNTIME === "node") {
53-
FIREBASE_ADMIN = "^12.0.0";
54-
}
52+
// TypeScript type guard to ensure TEST_RUNTIME is the correct type
53+
const validRuntimes = ["node", "python"] as const;
54+
type ValidRuntime = (typeof validRuntimes)[number];
55+
const runtime: ValidRuntime = TEST_RUNTIME as ValidRuntime;
5556

56-
if (!FIREBASE_ADMIN && TEST_RUNTIME === "python") {
57+
if (!FIREBASE_ADMIN && runtime === "node") {
58+
FIREBASE_ADMIN = "^12.0.0";
59+
} else if (!FIREBASE_ADMIN && runtime === "python") {
5760
FIREBASE_ADMIN = "6.5.0";
61+
} else if (!FIREBASE_ADMIN) {
62+
throw new Error("FIREBASE_ADMIN is not set");
5863
}
5964

60-
setup(TEST_RUNTIME as "node" | "python", TEST_RUN_ID, NODE_VERSION, FIREBASE_ADMIN!);
65+
setup(runtime, TEST_RUN_ID, NODE_VERSION, FIREBASE_ADMIN);
6166

6267
const config = {
6368
projectId: PROJECT_ID,
6469
projectDir: process.cwd(),
6570
sourceDir: `${process.cwd()}/functions`,
66-
runtime: TEST_RUNTIME === "node" ? "nodejs18" : "python311",
71+
runtime: runtime === "node" ? "nodejs18" : "python311",
6772
};
6873

6974
console.log("Firebase config created: ");
@@ -175,28 +180,6 @@ async function deployModifiedFunctions(): Promise<void> {
175180
}
176181
}
177182

178-
async function removeDeployedFunctions(functionNames: string[]): Promise<void> {
179-
console.log("Removing deployed functions...");
180-
181-
try {
182-
const options = {
183-
project: config.projectId,
184-
config: "./firebase.json",
185-
debug: true,
186-
nonInteractive: true,
187-
force: true,
188-
};
189-
190-
console.log("Removing functions with id:", TEST_RUN_ID);
191-
await client.functions.delete(functionNames, options);
192-
193-
console.log("Deployed functions have been removed.");
194-
} catch (err) {
195-
console.error("Error removing deployed functions. Exiting.", err);
196-
process.exit(1);
197-
}
198-
}
199-
200183
function cleanFiles(): void {
201184
console.log("Cleaning files...");
202185
const functionsDir = "functions";

integration_test/setup-local.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import setup from "./setup";
2-
import { loadEnv } from "./utils";
2+
import * as dotenv from "dotenv";
33

4-
loadEnv();
4+
dotenv.config();
55

66
setup("node", "local", "18", "^12.0.0");

integration_test/src/cleanup.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import fs from "fs";
2+
import { logError, logCleanup } from "./logger.js";
3+
4+
export function cleanFiles(testRunId: string): void {
5+
logCleanup("Cleaning files...");
6+
const functionsDir = "functions";
7+
process.chdir(functionsDir); // go to functions
8+
try {
9+
const files = fs.readdirSync(".");
10+
files.forEach((file) => {
11+
// For Node
12+
if (file.match(`firebase-functions-${testRunId}.tgz`)) {
13+
fs.rmSync(file);
14+
}
15+
// For Python
16+
if (file.match(`firebase_functions.tar.gz`)) {
17+
fs.rmSync(file);
18+
}
19+
if (file.match("package.json")) {
20+
fs.rmSync(file);
21+
}
22+
if (file.match("requirements.txt")) {
23+
fs.rmSync(file);
24+
}
25+
if (file.match("firebase-debug.log")) {
26+
fs.rmSync(file);
27+
}
28+
if (file.match("functions.yaml")) {
29+
fs.rmSync(file);
30+
}
31+
});
32+
33+
fs.rmSync("lib", { recursive: true, force: true });
34+
fs.rmSync("venv", { recursive: true, force: true });
35+
} catch (error) {
36+
logError("Error occurred while cleaning files:", error as Error);
37+
}
38+
39+
process.chdir("../"); // go back to integration_test
40+
}
41+
42+
export async function handleCleanUp(client: any, testRunId: string): Promise<void> {
43+
logCleanup("Cleaning up...");
44+
try {
45+
// Import postCleanup from deployment-utils
46+
const { postCleanup } = await import("../deployment-utils.js");
47+
await postCleanup(client, testRunId);
48+
} catch (err) {
49+
logError("Error during post-cleanup:", err as Error);
50+
// Don't throw here to ensure files are still cleaned
51+
}
52+
cleanFiles(testRunId);
53+
}
54+
55+
export function gracefulShutdown(cleanupFn: () => Promise<void>): void {
56+
console.log("SIGINT received...");
57+
cleanupFn()
58+
.then(() => {
59+
process.exit(1);
60+
})
61+
.catch((error) => {
62+
logError("Error during graceful shutdown:", error);
63+
process.exit(1);
64+
});
65+
}

integration_test/src/config.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import * as z from "zod/mini";
2+
3+
// Load English locale for better error messages
4+
z.config(z.locales.en());
5+
6+
export interface TestConfig {
7+
projectId: string;
8+
testRunId: string;
9+
runtime: "node" | "python";
10+
nodeVersion: string;
11+
firebaseAdmin: string;
12+
region: string;
13+
storageRegion: string;
14+
debug?: string;
15+
databaseUrl: string;
16+
storageBucket: string;
17+
firebaseAppId: string;
18+
firebaseMeasurementId: string;
19+
firebaseAuthDomain: string;
20+
firebaseApiKey: string;
21+
googleAnalyticsApiSecret: string;
22+
}
23+
24+
// Environment validation schema
25+
const environmentSchema = z.object({
26+
PROJECT_ID: z.string().check(z.minLength(1, "PROJECT_ID is required")),
27+
DATABASE_URL: z.string().check(z.minLength(1, "DATABASE_URL is required")),
28+
STORAGE_BUCKET: z.string().check(z.minLength(1, "STORAGE_BUCKET is required")),
29+
FIREBASE_APP_ID: z.string().check(z.minLength(1, "FIREBASE_APP_ID is required")),
30+
FIREBASE_MEASUREMENT_ID: z.string().check(z.minLength(1, "FIREBASE_MEASUREMENT_ID is required")),
31+
FIREBASE_AUTH_DOMAIN: z.string().check(z.minLength(1, "FIREBASE_AUTH_DOMAIN is required")),
32+
FIREBASE_API_KEY: z.string().check(z.minLength(1, "FIREBASE_API_KEY is required")),
33+
GOOGLE_ANALYTICS_API_SECRET: z
34+
.string()
35+
.check(z.minLength(1, "GOOGLE_ANALYTICS_API_SECRET is required")),
36+
TEST_RUNTIME: z.enum(["node", "python"]),
37+
NODE_VERSION: z.optional(z.string()),
38+
FIREBASE_ADMIN: z.optional(z.string()),
39+
REGION: z.optional(z.string()),
40+
STORAGE_REGION: z.optional(z.string()),
41+
DEBUG: z.optional(z.string()),
42+
});
43+
44+
/**
45+
* Validates that all required environment variables are set and have valid values.
46+
* Exits the process with code 1 if validation fails.
47+
*/
48+
export function validateEnvironment(): void {
49+
try {
50+
environmentSchema.parse(process.env);
51+
} catch (error) {
52+
console.error("Environment validation failed:");
53+
if (error && typeof error === "object" && "errors" in error) {
54+
const zodError = error as { errors: Array<{ path: string[]; message: string }> };
55+
zodError.errors.forEach((err) => {
56+
console.error(` ${err.path.join(".")}: ${err.message}`);
57+
});
58+
} else {
59+
console.error("Unexpected error during environment validation:", error);
60+
}
61+
process.exit(1);
62+
}
63+
}
64+
65+
/**
66+
* Loads and validates environment configuration, returning a typed config object.
67+
* @returns TestConfig object with all validated environment variables
68+
*/
69+
export function loadConfig(): TestConfig {
70+
// Validate environment first to ensure all required variables are set
71+
const validatedEnv = environmentSchema.parse(process.env);
72+
73+
// TypeScript type guard to ensure TEST_RUNTIME is the correct type
74+
const validRuntimes = ["node", "python"] as const;
75+
type ValidRuntime = (typeof validRuntimes)[number];
76+
const runtime: ValidRuntime = validatedEnv.TEST_RUNTIME;
77+
78+
let firebaseAdmin = validatedEnv.FIREBASE_ADMIN;
79+
if (!firebaseAdmin && runtime === "node") {
80+
firebaseAdmin = "^12.0.0";
81+
} else if (!firebaseAdmin && runtime === "python") {
82+
firebaseAdmin = "6.5.0";
83+
} else if (!firebaseAdmin) {
84+
throw new Error("FIREBASE_ADMIN is not set");
85+
}
86+
87+
const testRunId = `t${Date.now()}`;
88+
89+
return {
90+
projectId: validatedEnv.PROJECT_ID,
91+
testRunId,
92+
runtime,
93+
nodeVersion: validatedEnv.NODE_VERSION ?? "18",
94+
firebaseAdmin,
95+
region: validatedEnv.REGION ?? "us-central1",
96+
storageRegion: validatedEnv.STORAGE_REGION ?? "us-central1",
97+
debug: validatedEnv.DEBUG,
98+
databaseUrl: validatedEnv.DATABASE_URL,
99+
storageBucket: validatedEnv.STORAGE_BUCKET,
100+
firebaseAppId: validatedEnv.FIREBASE_APP_ID,
101+
firebaseMeasurementId: validatedEnv.FIREBASE_MEASUREMENT_ID,
102+
firebaseAuthDomain: validatedEnv.FIREBASE_AUTH_DOMAIN,
103+
firebaseApiKey: validatedEnv.FIREBASE_API_KEY,
104+
googleAnalyticsApiSecret: validatedEnv.GOOGLE_ANALYTICS_API_SECRET,
105+
};
106+
}
107+
108+
/**
109+
* Creates Firebase configuration object for deployment.
110+
* @param config - The test configuration object
111+
* @returns Firebase configuration object
112+
*/
113+
export function createFirebaseConfig(config: TestConfig) {
114+
return {
115+
projectId: config.projectId,
116+
projectDir: process.cwd(),
117+
sourceDir: `${process.cwd()}/functions`,
118+
runtime: config.runtime === "node" ? "nodejs18" : "python311",
119+
};
120+
}
121+
122+
/**
123+
* Creates environment configuration for function deployment.
124+
* @param config - The test configuration object
125+
* @returns Environment configuration object
126+
*/
127+
export function createEnvironmentConfig(config: TestConfig) {
128+
const firebaseConfig = {
129+
databaseURL: config.databaseUrl,
130+
projectId: config.projectId,
131+
storageBucket: config.storageBucket,
132+
};
133+
134+
return {
135+
DEBUG: config.debug,
136+
FIRESTORE_PREFER_REST: "true",
137+
GCLOUD_PROJECT: config.projectId,
138+
FIREBASE_CONFIG: JSON.stringify(firebaseConfig),
139+
REGION: config.region,
140+
STORAGE_REGION: config.storageRegion,
141+
};
142+
}

0 commit comments

Comments
 (0)