Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH3776,3777,3778: Introduce Directory/File IPath interface, GitHub Actions UploadArtifact relative path support , Add GitHub Actions DownloadArtifact command #3779

Merged
merged 4 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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