From a56290edc78fac7340660f0a27c067bffdef0cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Thu, 17 Apr 2025 08:57:21 +0000 Subject: [PATCH] Merged in fix/SW-2401-share-cache-in-prod (pull request #1815) Fix/SW-2401 share cache in prod * fix: reuse cache between prod and pre-prod * tests: add tests for generating cachePrefix * tests: remove unnecessary reset of process.env * tests: add tests for generateCacheKey * fix: make sure that we don't get invalid cacheKeys * fix: make sure that we don't get invalid cacheKeys Approved-by: Linus Flood --- .../dataCache/DistributedCache/endpoints.ts | 4 +- .../DistributedCache/generateCacheKey.ts | 20 ---- .../generateCacheKey/getBranchPrefix.test.ts | 29 +++++ .../generateCacheKey/getBranchPrefix.ts | 26 +++++ .../generateCacheKey/getPrefix.test.ts | 102 ++++++++++++++++++ .../generateCacheKey/getPrefix.ts | 29 +++++ .../generateCacheKey/index.test.ts | 46 ++++++++ .../generateCacheKey/index.ts | 15 +++ 8 files changed, 248 insertions(+), 23 deletions(-) delete mode 100644 apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey.ts create mode 100644 apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.test.ts create mode 100644 apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.ts create mode 100644 apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.test.ts create mode 100644 apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.ts create mode 100644 apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.test.ts create mode 100644 apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.ts diff --git a/apps/scandic-web/services/dataCache/DistributedCache/endpoints.ts b/apps/scandic-web/services/dataCache/DistributedCache/endpoints.ts index 95c5b64d9..e737f9b38 100644 --- a/apps/scandic-web/services/dataCache/DistributedCache/endpoints.ts +++ b/apps/scandic-web/services/dataCache/DistributedCache/endpoints.ts @@ -1,14 +1,12 @@ import { env } from "@/env/server" -import { generateCacheKey } from "./generateCacheKey" - export function getCacheEndpoint(key: string) { if (!env.REDIS_API_HOST) { throw new Error("REDIS_API_HOST is not set") } const url = new URL(`/api/cache`, env.REDIS_API_HOST) - url.searchParams.set("key", encodeURIComponent(generateCacheKey(key))) + url.searchParams.set("key", encodeURIComponent(key)) return url } diff --git a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey.ts b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey.ts deleted file mode 100644 index 2d858952e..000000000 --- a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { env } from "@/env/server" - -export function generateCacheKey(key: string | string[]): string { - const prefix = getPrefix() - key = Array.isArray(key) ? key.join("_") : key - - return `${prefix ? `${prefix}:` : ""}${key}` -} - -function getPrefix(): string { - if (process.env.NODE_ENV === "development") { - const devPrefix = process.env.USER || process.env.USERNAME || "dev" - return `${devPrefix}` - } - - const branch = env.BRANCH.trim() - const gitSha = env.GIT_SHA?.trim().substring(0, 7) - - return `${branch}:${gitSha}` -} diff --git a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.test.ts b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.test.ts new file mode 100644 index 000000000..9f381580a --- /dev/null +++ b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "@jest/globals" + +import { getBranchPrefix } from "./getBranchPrefix" + +describe("getBranchPrefix", () => { + it("should return empty string for production branches", () => { + expect(getBranchPrefix("production")).toBe("") + expect(getBranchPrefix("prod")).toBe("") + expect(getBranchPrefix("release")).toBe("") + expect(getBranchPrefix("release-v1")).toBe("") + expect(getBranchPrefix("release-v1.2")).toBe("") + expect(getBranchPrefix("release-v1.2.3")).toBe("") + expect(getBranchPrefix("release-v1.2.3-rc1")).toBe("") + expect(getBranchPrefix("release-v1.2-beta")).toBe("") + expect(getBranchPrefix("release-v1-preview")).toBe("") + }) + + it("should return branch name for non-production branches", () => { + expect(getBranchPrefix("feature/hello")).toBe("feature/hello") + expect(getBranchPrefix("fix/stuff")).toBe("fix/stuff") + expect(getBranchPrefix("releasee")).toBe("releasee") + expect(getBranchPrefix("release-vA")).toBe("release-vA") + expect(getBranchPrefix("release-v1.A")).toBe("release-v1.A") + expect(getBranchPrefix("release-v1.2.A")).toBe("release-v1.2.A") + expect(getBranchPrefix("release-v1.2.A-rc1")).toBe("release-v1.2.A-rc1") + expect(getBranchPrefix("release-v1.A-beta")).toBe("release-v1.A-beta") + expect(getBranchPrefix("release-vA-preview")).toBe("release-vA-preview") + }) +}) diff --git a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.ts b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.ts new file mode 100644 index 000000000..f536f215d --- /dev/null +++ b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getBranchPrefix.ts @@ -0,0 +1,26 @@ +/** + * This will match release branches + * @example + * release-v1.2.3 + * release-v1.2 + * release-v1 + * release-v1.2.3-alpha + * release-v1.2-beta + * release-v1-preview + */ +const releaseRegex = /^release-v\d+(?:\.\d+){0,2}(?:-\w+)?$/ + +/** + * If the branch is a production branch reuse the same prefix so that we can reuse the cache between pre-prod and prod + * @param branch + * @returns + */ +export const getBranchPrefix = (branch: string) => { + const isProdBranch = + branch === "production" || + branch === "prod" || + branch === "release" || + releaseRegex.test(branch) + + return isProdBranch ? "" : branch +} diff --git a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.test.ts b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.test.ts new file mode 100644 index 000000000..34954260c --- /dev/null +++ b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, it, jest } from "@jest/globals" + +jest.doMock("@/env/server", () => ({ + env: { + NODE_ENV: "test", + }, +})) + +const { env } = require("@/env/server") +const { getPrefix } = require("./getPrefix") + +const mockedEnv = env as { BRANCH: string; GIT_SHA: string } + +describe("getPrefix", () => { + const OLD_ENV = process.env + + beforeEach(() => { + jest.resetModules() + }) + + afterEach(() => { + process.env = OLD_ENV + }) + + it.each([ + "prod", + "production", + "release", + "release-v1", + "release-v2", + "release-v2-alpha", + "release-v2.1-alpha", + "release-v2.1.2-alpha", + ])( + "should return gitsha for production branches when name is '%s'", + (branchName: string) => { + mockedEnv.BRANCH = branchName + mockedEnv.GIT_SHA = "gitsha" + + const result = getPrefix() + + expect(result).toBe("gitsha") + } + ) + + it.each([ + "fix/stuff", + "feat/my-feature", + "feature/my-feature", + "releasee", + "release-vA", + "FEAT", + ])( + "should return branch name and gitsha for non-production branches when name is '%s'", + (branchName: string) => { + mockedEnv.BRANCH = branchName + mockedEnv.GIT_SHA = "gitsha" + + const result = getPrefix() + + expect(result).toBe(`${mockedEnv.BRANCH}:${mockedEnv.GIT_SHA}`) + } + ) + + it("should throw if BRANCH and/or GIT_SHA is not set", () => { + mockedEnv.BRANCH = "" + mockedEnv.GIT_SHA = "" + + expect(getPrefix).toThrow( + "Unable to getPrefix, BRANCH and GIT_SHA must be set" + ) + + mockedEnv.BRANCH = "hasBranch" + mockedEnv.GIT_SHA = "" + + expect(getPrefix).toThrow("Unable to getPrefix, GIT_SHA must be set") + + mockedEnv.BRANCH = "" + mockedEnv.GIT_SHA = "hasGitSha" + + expect(getPrefix).toThrow("Unable to getPrefix, BRANCH must be set") + }) + + it("should return dev or local user if running locally", () => { + ;(process.env.NODE_ENV as any) = "development" + + process.env.USER = "test_user" + process.env.USERNAME = "test_username" + + mockedEnv.BRANCH = "" + mockedEnv.GIT_SHA = "" + + expect(getPrefix()).toBe("test_user") + + process.env.USER = "" + + expect(getPrefix()).toBe("test_username") + + process.env.USERNAME = "" + expect(getPrefix()).toBe("dev") + }) +}) diff --git a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.ts b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.ts new file mode 100644 index 000000000..42dd39ccd --- /dev/null +++ b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/getPrefix.ts @@ -0,0 +1,29 @@ +import { env } from "@/env/server" + +import { getBranchPrefix } from "./getBranchPrefix" + +export function getPrefix(): string { + if (process.env.NODE_ENV === "development") { + const devPrefix = process.env.USER || process.env.USERNAME || "dev" + return `${devPrefix}` + } + + const branch = env.BRANCH.trim() + const gitSha = env.GIT_SHA?.trim().substring(0, 7) + + if (!branch && !gitSha) { + throw new Error("Unable to getPrefix, BRANCH and GIT_SHA must be set") + } + + if (!branch) { + throw new Error("Unable to getPrefix, BRANCH must be set") + } + + if (!gitSha) { + throw new Error("Unable to getPrefix, GIT_SHA must be set") + } + + const prefixTokens = [getBranchPrefix(branch), gitSha].filter(Boolean) + + return prefixTokens.join(":") +} diff --git a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.test.ts b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.test.ts new file mode 100644 index 000000000..dd42a2f96 --- /dev/null +++ b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it, jest } from "@jest/globals" + +jest.mock("./getPrefix", () => ({ + getPrefix: jest.fn(() => "gitsha"), +})) + +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +const indexModule: typeof import(".") = require(".") + +const { generateCacheKey } = indexModule + +describe("generateCacheKey", () => { + const OLD_ENV = process.env + + beforeEach(() => { + jest.resetModules() + }) + + afterEach(() => { + process.env = OLD_ENV + }) + + it("generates cachekey with prefix and key using string", () => { + expect(generateCacheKey("key1")).toBe("gitsha:key1") + }) + + it("generates cachekey with prefix and key using array", () => { + expect(generateCacheKey(["key1"])).toBe("gitsha:key1") + }) + + it("generates cachekey with prefix and keys", () => { + const actual = generateCacheKey(["key1", "key2"]) + expect(actual).toBe("gitsha:key1_key2") + }) + + it("should throw an error if no keys are provided", () => { + expect(() => generateCacheKey([])).toThrow("No keys provided") + }) + + it("should throw an error if only invalid keys are provided", () => { + expect(() => generateCacheKey(["", undefined, null] as string[])).toThrow( + "No keys provided" + ) + expect(() => generateCacheKey("")).toThrow("No keys provided") + }) +}) diff --git a/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.ts b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.ts new file mode 100644 index 000000000..5b0dc8cbb --- /dev/null +++ b/apps/scandic-web/services/dataCache/DistributedCache/generateCacheKey/index.ts @@ -0,0 +1,15 @@ +import { getPrefix } from "./getPrefix" + +export function generateCacheKey(key: string | string[]): string { + const keyArray = (Array.isArray(key) ? key : [key]).filter(Boolean) + + if (keyArray.length === 0) { + throw new Error("No keys provided") + } + + const prefix = getPrefix() + + const keyTokens = [prefix, keyArray.join("_")].filter(Boolean).join(":") + + return keyTokens +}