diff --git a/src/fixtures/sessionData.ts b/src/fixtures/sessionData.ts index 661515caa..49abb86b0 100644 --- a/src/fixtures/sessionData.ts +++ b/src/fixtures/sessionData.ts @@ -4,6 +4,7 @@ import GithubSessionData from "@root/classes/GithubSessionData" import UserSessionData from "@root/classes/UserSessionData" import UserWithSiteSessionData from "@root/classes/UserWithSiteSessionData" import { FeatureFlags } from "@root/types/featureFlags" +import { RawGitTreeEntry } from "@root/types/github" import { MOCK_USER_EMAIL_ONE, @@ -24,6 +25,23 @@ export const mockTreeSha = "mockTreeSha" export const mockCurrentCommitSha = "mockCurrentCommitSha" export const mockSiteName = "mockSiteName" export const mockGrowthBook = new GrowthBook() +export const gitTree: RawGitTreeEntry[] = [ + { + path: "directory/file1.txt", + type: "tree", + sha: "fake-sha-1", + mode: "100644", + url: "fake-url-1", + }, + { + path: "directory/file2.txt", + type: "file", + sha: "fake-sha-2", + mode: "100644", + url: "fake-url-2", + size: 100, + }, +] export const mockGithubState = { treeSha: mockTreeSha, diff --git a/src/services/db/__tests__/GitHubService.spec.ts b/src/services/db/__tests__/GitHubService.spec.ts index ba5cea7e2..5f711e969 100644 --- a/src/services/db/__tests__/GitHubService.spec.ts +++ b/src/services/db/__tests__/GitHubService.spec.ts @@ -15,9 +15,12 @@ import { mockCurrentCommitSha, mockGithubSessionData, mockIsomerUserId, + gitTree, } from "@fixtures/sessionData" +import { STAGING_BRANCH, STAGING_LITE_BRANCH } from "@root/constants" import { indexHtmlContent } from "@root/fixtures/markdown-fixtures" import { collectionYmlContent } from "@root/fixtures/yaml-fixtures" +import { RawGitTreeEntry } from "@root/types/github" import GitHubService from "@services/db/GitHubService" // using es6 gives some error @@ -871,7 +874,7 @@ describe("Github Service", () => { it("should update a repo tree correctly", async () => { const firstSha = "first-sha" const secondSha = "second-sha" - const gitTree = "git-tree" + const message = "message" const finalExpectedMessage = JSON.stringify({ message, @@ -996,4 +999,225 @@ describe("Github Service", () => { expect(resp.isOk()).toEqual(true) }) }) + + describe("deleteDirectory", () => { + const message = JSON.stringify({ + message: `Delete directory: ${directoryName}`, + directoryName, + userId, + }) + const params = { + message, + branch: BRANCH_REF, + sha: treeSha, + force: true, + } + + const endpoint = `${siteName}/git/refs/heads/${STAGING_BRANCH}` + const stagingLiteEndpoint = `${siteName}/git/refs/heads/${STAGING_LITE_BRANCH}` + + it("should delete a directory correctly", async () => { + // Arrange + const getTreeSpy = jest.spyOn(service, "getTree") + getTreeSpy.mockResolvedValueOnce(gitTree) + mockAxiosInstance.get.mockImplementation(() => + Promise.resolve({ data: gitTree }) + ) + const updateTreeSpy = jest.spyOn(service, "updateTree") + updateTreeSpy.mockResolvedValueOnce(treeSha) + // Act + await service.deleteDirectory(sessionData, { + directoryName, + message, + githubSessionData: mockGithubSessionData, + isStaging: true, + }) + + // Assert + expect(mockAxiosInstance.patch).toHaveBeenCalledWith( + endpoint, + { + force: true, + sha: treeSha, + }, + { + headers: { Authorization: `token ${accessToken}` }, + } + ) + }) + + it("should delete a directory correctly in staging lite branch", async () => { + // Arrange + const getTreeSpy = jest.spyOn(service, "getTree") + getTreeSpy.mockResolvedValueOnce(gitTree) + mockAxiosInstance.get.mockImplementation(() => + Promise.resolve({ data: gitTree }) + ) + const updateTreeSpy = jest.spyOn(service, "updateTree") + updateTreeSpy.mockResolvedValueOnce(treeSha) + // Act + await service.deleteDirectory(sessionData, { + directoryName, + message, + githubSessionData: mockGithubSessionData, + isStaging: false, + }) + + // Assert + expect(mockAxiosInstance.patch).toHaveBeenCalledWith( + stagingLiteEndpoint, + { + force: true, + sha: treeSha, + }, + { + headers: { Authorization: `token ${accessToken}` }, + } + ) + }) + + // it("should throw the correct error if directory cannot be found", async () => { + // // Arrange + // const getTreeSpy = jest.spyOn(service, "getTree") + // getTreeSpy.mockResolvedValueOnce(gitTree) + // const updateTreeSpy = jest.spyOn(service, "updateTree") + // updateTreeSpy.mockResolvedValueOnce(treeSha) + // mockAxiosInstance.patch.mockImplementation(() => { + // const err = { + // response: { + // status: 404, + // }, + // isAxiosError: true, + // } + // throw err + // }) + + // // Act + // await expect( + // service.deleteDirectory(sessionData, { + // directoryName, + // message, + // githubSessionData: mockGithubSessionData, + // isStaging: true, + // }) + // ).rejects.toThrow() + + // // Assert + // expect(mockAxiosInstance.patch).toHaveBeenCalledWith(endpoint, { + // params, + // headers: authHeader.headers, + // }) + // }) + + // it("should throw the correct error if directory cannot be found in staging lite", async () => { + // // Arrange + + // const getTreeSpy = jest.spyOn(service, "getTree") + // getTreeSpy.mockResolvedValueOnce(gitTree) + // const updateTreeSpy = jest.spyOn(service, "updateTree") + // updateTreeSpy.mockResolvedValueOnce(treeSha) + // mockAxiosInstance.patch.mockImplementation(() => { + // const err = { + // response: { + // status: 404, + // }, + // isAxiosError: true, + // } + // throw err + // }) + + // // Act + // await expect( + // service.deleteDirectory(sessionData, { + // directoryName, + // message, + // githubSessionData: mockGithubSessionData, + // isStaging: true, + // }) + // ).rejects.toThrow() + + // // Assert + // expect(mockAxiosInstance.patch).toHaveBeenCalledWith(endpoint, { + // params, + // headers: authHeader.headers, + // }) + // }) + }) + + describe("renameSinglePath", () => { + it("should rename a file correctly", async () => { + // Arrange + + const oldPath = "old/path.txt" + const newPath = "new/path.txt" + const message = "Renaming file" + const isStaging = true + + const newGitTree: RawGitTreeEntry[] = [ + { + path: newPath, + type: "file", + sha: "new-sha1", + mode: "100644", + url: "", + }, + { + path: oldPath, + type: "file", + sha: "new-sha2", + mode: "100644", + url: "", + }, + ] + + const resolvedTree = [ + { + path: newPath, + type: "file", + sha: "new-sha1", + mode: "100644", + url: "", + }, + { + path: oldPath, + type: "file", + sha: null, + mode: "100644", + url: "", + }, + ] + const newCommitSha = "new-commit-sha" + jest.spyOn(service, "getTree").mockResolvedValueOnce(newGitTree) + jest.spyOn(service, "updateTree").mockResolvedValueOnce(newCommitSha) + jest.spyOn(service, "updateRepoState").mockResolvedValueOnce() + + // Act + const result = await service.renameSinglePath( + sessionData, + mockGithubSessionData, + oldPath, + newPath, + message, + isStaging + ) + + // Assert + expect(service.getTree).toHaveBeenCalledWith( + sessionData, + mockGithubSessionData, + { isRecursive: true }, + isStaging + ) + expect(service.updateTree).toHaveBeenCalledWith( + sessionData, + mockGithubSessionData, + { gitTree: resolvedTree, message } + ) + expect(service.updateRepoState).toHaveBeenCalledWith(sessionData, { + commitSha: newCommitSha, + branchName: service.getBranch(isStaging), + }) + expect(result).toEqual({ newSha: newCommitSha }) + }) + }) }) diff --git a/src/services/db/__tests__/GithubCommitService.spec.ts b/src/services/db/__tests__/GithubCommitService.spec.ts new file mode 100644 index 000000000..6f16d9c81 --- /dev/null +++ b/src/services/db/__tests__/GithubCommitService.spec.ts @@ -0,0 +1,125 @@ +import { AxiosCacheInstance } from "axios-cache-interceptor" + +import { + mockAccessToken, + mockIsomerUserId, + mockSiteName, + mockTreeSha, + mockUserWithSiteSessionData, +} from "@root/fixtures/sessionData" + +import GitHubCommitService from "../GithubCommitService" + +// using es6 gives some error +const { Base64 } = require("js-base64") + +const BRANCH_REF = "staging" + +describe("Github Service", () => { + const siteName = mockSiteName + const accessToken = mockAccessToken + const fileName = "test-file" + const collectionName = "collection" + const subcollectionName = "subcollection" + const directoryName = `_${collectionName}` + const sha = "12345" + const treeSha = mockTreeSha + const content = "test-content" + + const userId = mockIsomerUserId + const subDirectoryName = `files/parent-file/sub-directory` + const subDirectoryFileName = ".keep" + const resourceCategoryName = "resources/some-folder" + const topLevelDirectoryFileName = "collection.yml" + const resourceCategoryFileName = "index.html" + + const sessionData = mockUserWithSiteSessionData + + const authHeader = { + headers: { + Authorization: `token ${accessToken}`, + }, + } + + const mockAxiosInstance = { + put: jest.fn(), + get: jest.fn(), + delete: jest.fn(), + post: jest.fn(), + patch: jest.fn(), + } + const service = new GitHubCommitService( + /** + * type casting here as it we only really need to mock the + * functions that we use + do not need to maintain a full + * list of axios functions + */ + (mockAxiosInstance as Partial) as AxiosCacheInstance + ) + + beforeEach(() => { + jest.clearAllMocks() + }) + + // describe("create", () => { + // const mockSuperCreate = jest.fn() + // const mockIsFileAsset = jest.fn() + // const mockIsReduceBuildTimesWhitelistedRepo = jest.fn() + + // // Mock the super.create method + // GitFileSystemService.prototype.create = mockSuperCreate + + // // Mock the isFileAsset and isReduceBuildTimesWhitelistedRepo functions + // global.isFileAsset = mockIsFileAsset + // global.isReduceBuildTimesWhitelistedRepo = mockIsReduceBuildTimesWhitelistedRepo + + // it("should create a file in the staging branch", async () => { + // const sessionData = { growthbook: "fake-repo" } + // const fileData = { + // content: "file content", + // fileName: "file.txt", + // directoryName: "directory", + // isMedia: false, + // } + + // mockSuperCreate.mockResolvedValueOnce({ sha: "fake-sha" }) + // mockIsFileAsset.mockReturnValueOnce(false) + // mockIsReduceBuildTimesWhitelistedRepo.mockReturnValueOnce(false) + + // const result = await GitFileSystemService.create(sessionData, fileData) + + // expect(mockSuperCreate).toHaveBeenCalledWith(sessionData, { + // ...fileData, + // branchName: STAGING_BRANCH, + // }) + // expect(result).toEqual({ sha: "fake-sha" }) + // }) + + // it("should create a file in both the staging and staging-lite branches if conditions are met", async () => { + // const sessionData = { growthbook: "fake-repo" } + // const fileData = { + // content: "file content", + // fileName: "file.txt", + // directoryName: "directory", + // isMedia: false, + // } + + // mockSuperCreate.mockResolvedValueOnce({ sha: "fake-sha" }) + // mockSuperCreate.mockResolvedValueOnce({ sha: "fake-sha-lite" }) + // mockIsFileAsset.mockReturnValueOnce(false) + // mockIsReduceBuildTimesWhitelistedRepo.mockReturnValueOnce(true) + + // const result = await GitFileSystemService.create(sessionData, fileData) + + // expect(mockSuperCreate).toHaveBeenCalledWith(sessionData, { + // ...fileData, + // branchName: STAGING_BRANCH, + // }) + // expect(mockSuperCreate).toHaveBeenCalledWith(sessionData, { + // ...fileData, + // branchName: STAGING_LITE_BRANCH, + // }) + // expect(result).toEqual({ sha: "fake-sha" }) + // }) + // }) +})