Fixes #529 Collect asset URLs into output
This commit is contained in:
@@ -15,6 +15,7 @@ const TEST_URLS = {
|
||||
} as const
|
||||
|
||||
const applyReleaseDataMock = jest.fn()
|
||||
const applyAssetUrlsMock = jest.fn()
|
||||
const artifactDestroyMock = jest.fn()
|
||||
const createMock = jest.fn()
|
||||
const deleteMock = jest.fn()
|
||||
@@ -51,6 +52,8 @@ const generatedReleaseBody = "test release notes"
|
||||
|
||||
describe("Action", () => {
|
||||
beforeEach(() => {
|
||||
applyReleaseDataMock.mockClear()
|
||||
applyAssetUrlsMock.mockClear()
|
||||
createMock.mockClear()
|
||||
getMock.mockClear()
|
||||
listMock.mockClear()
|
||||
@@ -77,6 +80,7 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({})
|
||||
})
|
||||
|
||||
it("creates release if no release exists to update", async () => {
|
||||
@@ -99,6 +103,10 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("creates release if no draft releases", async () => {
|
||||
@@ -124,6 +132,10 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("creates release then uploads artifact", async () => {
|
||||
@@ -144,6 +156,10 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("removes all artifacts when artifact destroyer is enabled", async () => {
|
||||
@@ -153,6 +169,10 @@ describe("Action", () => {
|
||||
|
||||
expect(artifactDestroyMock).toHaveBeenCalledWith(releaseId)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("removes no artifacts when artifact destroyer is disabled", async () => {
|
||||
@@ -162,6 +182,10 @@ describe("Action", () => {
|
||||
|
||||
expect(artifactDestroyMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("skips action", async () => {
|
||||
@@ -326,6 +350,10 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("updates draft release with static body", async () => {
|
||||
@@ -354,6 +382,10 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("updates release but does not upload if no artifact", async () => {
|
||||
@@ -374,6 +406,7 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({})
|
||||
})
|
||||
|
||||
it("updates release then uploads artifact", async () => {
|
||||
@@ -394,6 +427,10 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("updates release with static body when generateReleaseNotes is true but omitBodyDuringUpdate is true", async () => {
|
||||
@@ -422,6 +459,10 @@ describe("Action", () => {
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
function assertOutputApplied() {
|
||||
@@ -434,6 +475,10 @@ describe("Action", () => {
|
||||
})
|
||||
}
|
||||
|
||||
function assertAssetUrlsApplied(expectedUrls: Record<string, string>) {
|
||||
expect(applyAssetUrlsMock).toHaveBeenCalledWith(expectedUrls)
|
||||
}
|
||||
|
||||
function createAction(
|
||||
allowUpdates: boolean,
|
||||
hasArtifact: boolean,
|
||||
@@ -495,7 +540,10 @@ describe("Action", () => {
|
||||
zipball_url: TEST_URLS.ZIPBALL_URL,
|
||||
},
|
||||
})
|
||||
uploadMock.mockResolvedValue({})
|
||||
uploadMock.mockResolvedValue({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
|
||||
const MockInputs = jest.fn<Inputs, any>(() => {
|
||||
return {
|
||||
@@ -528,6 +576,7 @@ describe("Action", () => {
|
||||
const MockOutputs = jest.fn<Outputs, any>(() => {
|
||||
return {
|
||||
applyReleaseData: applyReleaseDataMock,
|
||||
applyAssetUrls: applyAssetUrlsMock,
|
||||
}
|
||||
})
|
||||
const MockUploader = jest.fn<ArtifactUploader, any>(() => {
|
||||
|
||||
@@ -13,6 +13,15 @@ const deleteMock = jest.fn()
|
||||
const listArtifactsMock = jest.fn()
|
||||
const uploadMock = jest.fn()
|
||||
|
||||
// Mock response with browser_download_url
|
||||
const mockUploadResponse = (name: string) => ({
|
||||
data: {
|
||||
browser_download_url: `https://github.com/octocat/Hello-World/releases/download/v1.0.0/${name}`,
|
||||
name: name,
|
||||
id: 1,
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock("fs", () => {
|
||||
const originalFs = jest.requireActual("fs")
|
||||
return {
|
||||
@@ -32,13 +41,48 @@ describe("ArtifactUploader", () => {
|
||||
uploadMock.mockClear()
|
||||
})
|
||||
|
||||
it("returns asset URLs when upload succeeds", async () => {
|
||||
mockListWithoutAssets()
|
||||
mockUploadSuccess()
|
||||
const uploader = createUploader(true)
|
||||
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it("returns empty object when no artifacts are uploaded", async () => {
|
||||
const uploader = createUploader(true)
|
||||
|
||||
const result = await uploader.uploadArtifacts([], releaseId, url)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("excludes failed uploads from returned URLs", async () => {
|
||||
mockListWithoutAssets()
|
||||
mockUploadArtifact(401, 2)
|
||||
const uploader = createUploader(true)
|
||||
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it("abort when upload failed with non-5xx response", async () => {
|
||||
mockListWithoutAssets()
|
||||
mockUploadArtifact(401, 2)
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
@@ -51,8 +95,9 @@ describe("ArtifactUploader", () => {
|
||||
mockUploadArtifact(500, 4)
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(5)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
@@ -66,11 +111,15 @@ describe("ArtifactUploader", () => {
|
||||
it("replaces all artifacts", async () => {
|
||||
mockDeleteSuccess()
|
||||
mockListWithAssets()
|
||||
mockUploadArtifact()
|
||||
mockUploadSuccess()
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
@@ -83,11 +132,15 @@ describe("ArtifactUploader", () => {
|
||||
it("replaces no artifacts when previous asset list empty", async () => {
|
||||
mockDeleteSuccess()
|
||||
mockListWithoutAssets()
|
||||
mockUploadArtifact()
|
||||
mockUploadSuccess()
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
@@ -100,8 +153,9 @@ describe("ArtifactUploader", () => {
|
||||
mockUploadArtifact(500, 2)
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(4)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
@@ -127,7 +181,7 @@ describe("ArtifactUploader", () => {
|
||||
it("throws error from replace", async () => {
|
||||
mockDeleteError()
|
||||
mockListWithAssets()
|
||||
mockUploadArtifact()
|
||||
mockUploadSuccess()
|
||||
const uploader = createUploader(true)
|
||||
|
||||
expect.hasAssertions()
|
||||
@@ -141,11 +195,15 @@ describe("ArtifactUploader", () => {
|
||||
it("updates all artifacts, delete none", async () => {
|
||||
mockDeleteError()
|
||||
mockListWithAssets()
|
||||
mockUploadArtifact()
|
||||
mockUploadSuccess()
|
||||
const uploader = createUploader(false)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
@@ -194,6 +252,10 @@ describe("ArtifactUploader", () => {
|
||||
listArtifactsMock.mockResolvedValue([])
|
||||
}
|
||||
|
||||
function mockUploadSuccess() {
|
||||
uploadMock.mockImplementation((_, __, ___, ____, name) => Promise.resolve(mockUploadResponse(name)))
|
||||
}
|
||||
|
||||
function mockUploadArtifact(status = 200, failures = 0) {
|
||||
const error = new RequestError(`HTTP ${status}`, status, {
|
||||
headers: {},
|
||||
|
||||
@@ -70,6 +70,9 @@ describe.skip("Integration Test", () => {
|
||||
applyReleaseData(releaseData: ReleaseData) {
|
||||
console.log(`Release Data: ${releaseData}`)
|
||||
},
|
||||
applyAssetUrls(assetUrls: Record<string, string>) {
|
||||
console.log(`Asset URLs: ${JSON.stringify(assetUrls)}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
return new MockOutputs()
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
const mockSetOutput = jest.fn()
|
||||
import { CoreOutputs, type Outputs } from "../src/Outputs"
|
||||
import type { ReleaseData } from "../src/Releases"
|
||||
|
||||
import { CoreOutputs, Outputs } from "../src/Outputs"
|
||||
import { ReleaseData } from "../src/Releases"
|
||||
|
||||
jest.mock("@actions/core", () => {
|
||||
return { setOutput: mockSetOutput }
|
||||
})
|
||||
jest.mock("@actions/core")
|
||||
const { setOutput: mockSetOutput } = jest.mocked(require("@actions/core"))
|
||||
|
||||
const TEST_URLS = {
|
||||
HTML_URL: "https://api.example.com/assets",
|
||||
@@ -48,4 +45,19 @@ describe("Outputs", () => {
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("tarball_url", "")
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("zipball_url", "")
|
||||
})
|
||||
|
||||
it("Applies asset URLs to the action output", () => {
|
||||
const assetUrls = {
|
||||
"example.zip": "https://github.com/owner/repo/releases/download/v1.0.0/example.zip",
|
||||
"example.tar.gz": "https://github.com/owner/repo/releases/download/v1.0.0/example.tar.gz",
|
||||
}
|
||||
outputs.applyAssetUrls(assetUrls)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("assets", JSON.stringify(assetUrls))
|
||||
})
|
||||
|
||||
it("Applies empty asset URLs to the action output", () => {
|
||||
const assetUrls = {}
|
||||
outputs.applyAssetUrls(assetUrls)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("assets", JSON.stringify(assetUrls))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user