Skip to content

Commit

Permalink
Merge pull request #3779 from devlead/feature/gh-3776-3777-3778
Browse files Browse the repository at this point in the history
GH3776,3777,3778: Introduce Directory/File IPath interface, GitHub Actions UploadArtifact relative path support , Add GitHub Actions DownloadArtifact command
  • Loading branch information
devlead authored Jan 20, 2022
2 parents 03ebb24 + 75ebc4e commit 50dc560
Show file tree
Hide file tree
Showing 10 changed files with 592 additions and 59 deletions.
111 changes: 103 additions & 8 deletions src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Cake.Common.Build.GitHubActions.Commands;
using Cake.Common.Build.GitHubActions.Data;
using Cake.Core;
using Cake.Core.IO;
using Cake.Testing;
using NSubstitute;

Expand All @@ -16,6 +17,8 @@ internal sealed class GitHubActionsCommandsFixture : HttpMessageHandler
{
private const string ApiVersion = "6.0-preview";
private const string AcceptHeader = "application/json; api-version=" + ApiVersion;
private const string AcceptGzip = "application/octet-stream; api-version=" + ApiVersion;
private const string AcceptEncodingGzip = "gzip";
private const string CreateArtifactUrl = GitHubActionsInfoFixture.ActionRuntimeUrl +
"_apis/pipelines/workflows/34058136/artifacts?api-version=" + ApiVersion + "&artifactName=artifact";
private const string CreateArtifactsUrl = GitHubActionsInfoFixture.ActionRuntimeUrl +
Expand Down Expand Up @@ -54,6 +57,54 @@ internal sealed class GitHubActionsCommandsFixture : HttpMessageHandler
private const string PutDirectoryFolderBFolderCUrl = GitHubActionsInfoFixture.ActionRuntimeUrl +
"_apis/resources/Containers/942031?itemPath=artifacts%2Ffolder_b%2Ffolder_c%2Fartifact.txt";

private const string GetArtifactResourceUrl = GitHubActionsInfoFixture.ActionRuntimeUrl +
"_apis/pipelines/workflows/34058136/artifacts?api-version=6.0-preview&artifactName=artifact";
private const string FileContainerResourceUrl = GitHubActionsInfoFixture.ActionRuntimeUrl + @"_apis/resources/Containers/4794789";
private const string GetArtifactResourceResponse = @"{
""count"": 1,
""value"": [
{
""containerId"": 4794789,
""size"": 4,
""signedContent"": null,
""fileContainerResourceUrl"": """ + FileContainerResourceUrl + @""",
""type"": ""actions_storage"",
""name"": ""artifact"",
""url"": """ + GitHubActionsInfoFixture.ActionRuntimeUrl + @"_apis/pipelines/1/runs/7/artifacts?artifactName=artifact"",
""expiresOn"": ""2022-03-16T08:22:01.5699067Z"",
""items"": null
}
]
}";

private const string GetContainerItemResourcesUrl = FileContainerResourceUrl + "?itemPath=artifact";
private const string GetContainerItemResourcesResponse = @"{
""count"": 1,
""value"": [
{
""containerId"": 4794789,
""scopeIdentifier"": ""00000000-0000-0000-0000-000000000000"",
""path"": ""artifact/test.txt"",
""itemType"": ""file"",
""status"": ""created"",
""fileLength"": 4,
""fileEncoding"": 1,
""fileType"": 1,
""dateCreated"": ""2021-12-16T09:05:18.803Z"",
""dateLastModified"": ""2021-12-16T09:05:18.907Z"",
""createdBy"": ""2daeb16b-86ae-4e46-ba89-92a8aa076e52"",
""lastModifiedBy"": ""2daeb16b-86ae-4e46-ba89-92a8aa076e52"",
""itemLocation"": """ + GetContainerItemResourcesUrl + @"%2Ftest.txt&metadata=True"",
""contentLocation"": """ + GetContainerItemResourcesUrl + @"%2Ftest.txt"",
""fileId"": 1407,
""contentId"": """"
}
]
}";

private const string DownloadItemResourceUrl = GetContainerItemResourcesUrl + "%2Ftest.txt";
private const string DownloadItemResourceResponse = "Cake";

private GitHubActionsInfoFixture GitHubActionsInfoFixture { get; }
private ICakeEnvironment Environment { get; }
public FakeFileSystem FileSystem { get; }
Expand All @@ -71,6 +122,12 @@ public GitHubActionsCommands CreateGitHubActionsCommands()
return new GitHubActionsCommands(Environment, FileSystem, GitHubActionsInfoFixture.CreateEnvironmentInfo(), CreateClient);
}

public GitHubActionsCommandsFixture WithWorkingDirectory(DirectoryPath workingDirectory)
{
Environment.WorkingDirectory = workingDirectory;
return this;
}

public GitHubActionsCommandsFixture WithNoGitHubEnv()
{
Environment.GetEnvironmentVariable("GITHUB_ENV").Returns(null as string);
Expand All @@ -95,18 +152,26 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
};
}

if (!request.Headers.TryGetValues("Accept", out var values) || !values.Contains(AcceptHeader))
if (
!request.Headers.TryGetValues("Accept", out var values)
|| !values.Contains(AcceptHeader))
{
return new HttpResponseMessage
if (request.RequestUri.AbsoluteUri != DownloadItemResourceUrl
|| !values.Contains(AcceptGzip)
|| !request.Headers.TryGetValues("Accept-Encoding", out var encodingValues)
|| !encodingValues.Contains(AcceptEncodingGzip))
{
StatusCode = HttpStatusCode.BadRequest
};
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest
};
}
}

switch (request)
{
#pragma warning disable SA1013
// FilePath
// Create Artifact FilePath
case
{
RequestUri: { AbsoluteUri: CreateArtifactUrl },
Expand All @@ -116,7 +181,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
return Ok(new StringContent(CreateArtifactResponse));
}

// DirectoryPath
// Create Artifact DirectoryPath
case
{
RequestUri: { AbsoluteUri: CreateArtifactsUrl },
Expand All @@ -126,7 +191,37 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
return Ok(new StringContent(CreateArtifactsResponse));
}

// FilePath
// Download Artifact - Get Artifact Container Resource
case
{
RequestUri: { AbsoluteUri: GetArtifactResourceUrl },
Method: { Method: "GET" }
}:
{
return Ok(new StringContent(GetArtifactResourceResponse));
}

// Download Artifact - Get Artifact Container Item Resource
case
{
RequestUri: { AbsoluteUri: GetContainerItemResourcesUrl },
Method: { Method: "GET" }
}:
{
return Ok(new StringContent(GetContainerItemResourcesResponse));
}

// Download Artifact - DownloadItemResource
case
{
RequestUri: { AbsoluteUri: DownloadItemResourceUrl },
Method: { Method: "GET" }
}:
{
return Ok(new StringContent(DownloadItemResourceResponse));
}

// Put FilePath
case
{
RequestUri: { AbsoluteUri: PutFileUrl },
Expand All @@ -138,7 +233,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
Method: { Method: "PATCH" },
}:

// DirectoryPath
// Put DirectoryPath
case
{
RequestUri: { AbsoluteUri: PutDirectoryRootUrl },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,20 +233,24 @@ public async Task Should_Throw_If_File_Missing()
AssertEx.IsExceptionWithMessage<FileNotFoundException>(result, "Artifact file not found.");
}

[Fact]
public async Task Should_Upload()
[Theory]
[InlineData("/", "/artifacts/artifact.txt")]
[InlineData("/artifacts", "artifact.txt")]
public async Task Should_Upload(string workingDirectory, string testPath)
{
// Given
var gitHubActionsCommandsFixture = new GitHubActionsCommandsFixture();
var gitHubActionsCommandsFixture = new GitHubActionsCommandsFixture()
.WithWorkingDirectory(workingDirectory);
var testFilePath = FilePath.FromString(testPath);
var artifactName = "artifact";
var file = gitHubActionsCommandsFixture
gitHubActionsCommandsFixture
.FileSystem
.CreateFile("/artifacts/artifact.txt")
.SetContent(artifactName);
var commands = gitHubActionsCommandsFixture.CreateGitHubActionsCommands();

// When
await commands.UploadArtifact(file.Path, artifactName);
await commands.UploadArtifact(testFilePath, artifactName);
}
}

Expand Down Expand Up @@ -295,13 +299,17 @@ public async Task Should_Throw_If_Directory_Missing()
AssertEx.IsExceptionWithMessage<DirectoryNotFoundException>(result, "Artifact directory /artifacts not found.");
}

[Fact]
public async Task Should_Upload()
[Theory]
[InlineData("/", "/src/artifacts")]
[InlineData("/src", "artifacts")]
public async Task Should_Upload(string workingDirectory, string testPath)
{
// Given
var gitHubActionsCommandsFixture = new GitHubActionsCommandsFixture();
var gitHubActionsCommandsFixture = new GitHubActionsCommandsFixture()
.WithWorkingDirectory(workingDirectory);
var testDirectoryPath = DirectoryPath.FromString(testPath);
var artifactName = "artifacts";
var directory = DirectoryPath.FromString("/artifacts");
var directory = DirectoryPath.FromString("/src/artifacts");

gitHubActionsCommandsFixture
.FileSystem
Expand All @@ -326,9 +334,85 @@ public async Task Should_Upload()
var commands = gitHubActionsCommandsFixture.CreateGitHubActionsCommands();

// When
await commands.UploadArtifact(directory, artifactName);
await commands.UploadArtifact(testDirectoryPath, artifactName);
}
}
}

public sealed class TheDownloadArtifactMethod
{
[Fact]
public async Task Should_Throw_If_ArtifactName_Is_Null()
{
// Given
var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands();
var path = DirectoryPath.FromString("/artifacts");

// When
var result = await Record.ExceptionAsync(() => commands.DownloadArtifact(null, path));

// Then
AssertEx.IsArgumentNullException(result, "artifactName");
}

[Fact]
public async Task Should_Throw_If_Path_Is_Null()
{
// Given
var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands();
var artifactName = "artifactName";

// When
var result = await Record.ExceptionAsync(() => commands.DownloadArtifact(artifactName, null));

// Then
AssertEx.IsArgumentNullException(result, "path");
}

[Fact]
public async Task Should_Throw_If_Directory_Missing()
{
// Given
var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands();
var path = DirectoryPath.FromString("/artifacts");
var artifactName = "artifact";

// When
var result = await Record.ExceptionAsync(() => commands.DownloadArtifact(artifactName, path));

// Then
AssertEx.IsExceptionWithMessage<DirectoryNotFoundException>(result, "Local directory /artifacts not found.");
}

[Theory]
[InlineData("/", "/src/artifacts")]
[InlineData("/src", "artifacts")]
public async Task Should_Download(string workingDirectory, string testPath)
{
// Given
var gitHubActionsCommandsFixture = new GitHubActionsCommandsFixture()
.WithWorkingDirectory(workingDirectory);
var testDirectoryPath = DirectoryPath.FromString(testPath);
var artifactName = "artifact";
var directory = DirectoryPath.FromString("/src/artifacts");
var filePath = directory.CombineWithFilePath("test.txt");

gitHubActionsCommandsFixture
.FileSystem
.CreateDirectory(directory);

var commands = gitHubActionsCommandsFixture.CreateGitHubActionsCommands();

// When
await commands.DownloadArtifact(artifactName, testDirectoryPath);
var file = gitHubActionsCommandsFixture
.FileSystem
.GetFile(filePath);

// Then
Assert.True(file.Exists, $"{filePath.FullPath} doesn't exist.");
Assert.Equal("Cake", file.GetTextContent());
}
}
}
}
33 changes: 33 additions & 0 deletions src/Cake.Common/Build/GitHubActions/Commands/ContainerItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Text.Json.Serialization;

namespace Cake.Common.Build.GitHubActions.Commands
{
internal sealed class ContainerItem
{
[JsonPropertyName("containerId")]
public long ContainerId { get; set; }

[JsonPropertyName("size")]
public long Size { get; set; }

[JsonPropertyName("fileContainerResourceUrl")]
public string FileContainerResourceUrl { get; set; }

[JsonPropertyName("type")]
public string Type { get; set; }

[JsonPropertyName("name")]
public string Name { get; set; }

[JsonPropertyName("url")]
public string Url { get; set; }

[JsonPropertyName("expiresOn")]
public DateTimeOffset ExpiresOn { get; set; }
}
}
Loading

0 comments on commit 50dc560

Please sign in to comment.