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
This commit is contained in:
Joakim Jäderberg
2025-04-17 08:57:21 +00:00
parent 213bd2c561
commit a56290edc7
8 changed files with 248 additions and 23 deletions

View File

@@ -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")
})
})

View File

@@ -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
}

View File

@@ -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")
})
})

View File

@@ -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(":")
}

View File

@@ -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")
})
})

View File

@@ -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
}