Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04ee71a5d1 | |||
| 4342a1b8f0 | |||
| 5806ab55db | |||
| cb8124fc4a | |||
| 182a433782 |
+356
-441
File diff suppressed because it is too large
Load Diff
@@ -32,19 +32,19 @@
|
||||
"author": "GitHub Actions",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/glob": "^0.4.0"
|
||||
"@actions/glob": "^0.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^5.10.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.58.1",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.5.0",
|
||||
"lint-staged": "^16.4.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^6.0.2"
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.3" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -892,17 +892,18 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
Trace.Info("Initializing Job context");
|
||||
var jobContext = new JobContext();
|
||||
if (Global.Variables.GetBoolean(Constants.Runner.Features.AddCheckRunIdToJobContext) ?? false)
|
||||
ExpressionValues.TryGetValue("job", out var jobDictionary);
|
||||
if (jobDictionary != null)
|
||||
{
|
||||
ExpressionValues.TryGetValue("job", out var jobDictionary);
|
||||
if (jobDictionary != null)
|
||||
foreach (var pair in jobDictionary.AssertDictionary("job"))
|
||||
{
|
||||
foreach (var pair in jobDictionary.AssertDictionary("job"))
|
||||
{
|
||||
jobContext[pair.Key] = pair.Value;
|
||||
}
|
||||
jobContext[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Derive workflow_repository and workflow_file_path from workflow_ref
|
||||
// if the server sent workflow_ref but not the decomposed fields
|
||||
jobContext.DeriveWorkflowRefComponents();
|
||||
ExpressionValues["job"] = jobContext;
|
||||
|
||||
Trace.Info("Initialize GitHub context");
|
||||
|
||||
@@ -82,5 +82,115 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string WorkflowRef
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.TryGetValue("workflow_ref", out var value) && value is StringContextData str)
|
||||
{
|
||||
return str.Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
this["workflow_ref"] = value != null ? new StringContextData(value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public string WorkflowSha
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.TryGetValue("workflow_sha", out var value) && value is StringContextData str)
|
||||
{
|
||||
return str.Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
this["workflow_sha"] = value != null ? new StringContextData(value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public string WorkflowRepository
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.TryGetValue("workflow_repository", out var value) && value is StringContextData str)
|
||||
{
|
||||
return str.Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
this["workflow_repository"] = value != null ? new StringContextData(value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public string WorkflowFilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.TryGetValue("workflow_file_path", out var value) && value is StringContextData str)
|
||||
{
|
||||
return str.Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
this["workflow_file_path"] = value != null ? new StringContextData(value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a composite workflow_ref (e.g. "owner/repo/.github/workflows/file.yml@refs/heads/main")
|
||||
/// and populates workflow_repository and workflow_file_path if they are not already set.
|
||||
/// </summary>
|
||||
public void DeriveWorkflowRefComponents()
|
||||
{
|
||||
var workflowRef = WorkflowRef;
|
||||
if (string.IsNullOrEmpty(workflowRef))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Format: owner/repo/.github/workflows/file.yml@ref
|
||||
var atIndex = workflowRef.IndexOf('@');
|
||||
var pathPart = atIndex >= 0 ? workflowRef.Substring(0, atIndex) : workflowRef;
|
||||
|
||||
// Split at /.github/workflows/ to correctly handle repos named ".github"
|
||||
// e.g. "octo-org/.github/.github/workflows/ci.yml" → repo="octo-org/.github"
|
||||
var marker = "/.github/workflows/";
|
||||
var markerIndex = pathPart.IndexOf(marker);
|
||||
if (markerIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var repo = pathPart.Substring(0, markerIndex);
|
||||
var filePath = pathPart.Substring(markerIndex + 1); // skip leading '/'
|
||||
|
||||
// Validate repo is owner/repo format (must have at least one slash with non-empty segments)
|
||||
var slashIndex = repo.IndexOf('/');
|
||||
if (slashIndex <= 0 || slashIndex >= repo.Length - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (WorkflowRepository == null || WorkflowRepository == "")
|
||||
{
|
||||
WorkflowRepository = repo;
|
||||
}
|
||||
|
||||
if (WorkflowFilePath == null || WorkflowFilePath == "")
|
||||
{
|
||||
WorkflowFilePath = filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.3" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="10.0.3" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.DevTunnels.Connections" Version="1.3.16" />
|
||||
</ItemGroup>
|
||||
|
||||
+3
-3
@@ -23,14 +23,14 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.2" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.3" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="10.0.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1203,19 +1203,19 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this test can be deleted when `AddCheckRunIdToJobContext` is fully rolled out
|
||||
// AddCheckRunIdToJobContext is now permanently enabled server-side (hardcoded to "true"
|
||||
// in acquirejobhandler.go). The runner always copies ContextData["job"] entries, so the
|
||||
// flag-disabled test is no longer applicable. Replaced with a test that verifies
|
||||
// check_run_id is always hydrated regardless of the flag value.
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void InitializeJob_HydratesJobContextWithCheckRunId_FeatureFlagDisabled()
|
||||
public void InitializeJob_HydratesJobContextWithCheckRunId_AlwaysCopied()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange: Create a job request message and make sure the feature flag is disabled
|
||||
var variables = new Dictionary<string, VariableValue>()
|
||||
{
|
||||
[Constants.Runner.Features.AddCheckRunIdToJobContext] = new VariableValue("false"),
|
||||
};
|
||||
// Arrange: No feature flag set at all
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
var pagingLogger = new Moq.Mock<IPagingLogger>();
|
||||
var jobServerQueue = new Moq.Mock<IJobServerQueue>();
|
||||
@@ -1233,9 +1233,115 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
// Act
|
||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
// Assert: check_run_id is always copied regardless of flag
|
||||
Assert.NotNull(ec.JobContext);
|
||||
Assert.Null(ec.JobContext.CheckRunId); // with the feature flag disabled we should not have added a CheckRunId to the JobContext
|
||||
Assert.Equal(123456, ec.JobContext.CheckRunId);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void InitializeJob_HydratesJobContextWithWorkflowIdentity()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
var pagingLogger = new Moq.Mock<IPagingLogger>();
|
||||
var jobServerQueue = new Moq.Mock<IJobServerQueue>();
|
||||
hc.EnqueueInstance(pagingLogger.Object);
|
||||
hc.SetSingleton(jobServerQueue.Object);
|
||||
var ec = new Runner.Worker.ExecutionContext();
|
||||
ec.Initialize(hc);
|
||||
|
||||
// Arrange: Add workflow identity to the job context
|
||||
var jobContext = new Pipelines.ContextData.DictionaryContextData();
|
||||
jobContext["workflow_ref"] = new StringContextData("my-org/my-repo/.github/workflows/reusable.yml@refs/heads/main");
|
||||
jobContext["workflow_sha"] = new StringContextData("abc123def456");
|
||||
jobRequest.ContextData["job"] = jobContext;
|
||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
|
||||
// Act
|
||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||
|
||||
// Assert: direct properties from server
|
||||
Assert.NotNull(ec.JobContext);
|
||||
Assert.Equal("my-org/my-repo/.github/workflows/reusable.yml@refs/heads/main", ec.JobContext.WorkflowRef);
|
||||
Assert.Equal("abc123def456", ec.JobContext.WorkflowSha);
|
||||
|
||||
// Assert: derived properties
|
||||
Assert.Equal("my-org/my-repo", ec.JobContext.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/reusable.yml", ec.JobContext.WorkflowFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void InitializeJob_WorkflowIdentityNotSet_WhenServerSendsNoData()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange: Server sends no workflow identity in job context
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
var pagingLogger = new Moq.Mock<IPagingLogger>();
|
||||
var jobServerQueue = new Moq.Mock<IJobServerQueue>();
|
||||
hc.EnqueueInstance(pagingLogger.Object);
|
||||
hc.SetSingleton(jobServerQueue.Object);
|
||||
var ec = new Runner.Worker.ExecutionContext();
|
||||
ec.Initialize(hc);
|
||||
|
||||
// Arrange: empty job context
|
||||
jobRequest.ContextData["job"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
|
||||
// Act
|
||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||
|
||||
// Assert: no workflow identity
|
||||
Assert.NotNull(ec.JobContext);
|
||||
Assert.Null(ec.JobContext.WorkflowRef);
|
||||
Assert.Null(ec.JobContext.WorkflowSha);
|
||||
Assert.Null(ec.JobContext.WorkflowRepository);
|
||||
Assert.Null(ec.JobContext.WorkflowFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void InitializeJob_WorkflowIdentityDerived_WhenServerSendsAllFields()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange: Server sends all 4 fields explicitly
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
var pagingLogger = new Moq.Mock<IPagingLogger>();
|
||||
var jobServerQueue = new Moq.Mock<IJobServerQueue>();
|
||||
hc.EnqueueInstance(pagingLogger.Object);
|
||||
hc.SetSingleton(jobServerQueue.Object);
|
||||
var ec = new Runner.Worker.ExecutionContext();
|
||||
ec.Initialize(hc);
|
||||
|
||||
// Arrange: Server sends all fields, derivation should not overwrite
|
||||
var jobContext = new Pipelines.ContextData.DictionaryContextData();
|
||||
jobContext["workflow_ref"] = new StringContextData("my-org/my-repo/.github/workflows/reusable.yml@refs/heads/main");
|
||||
jobContext["workflow_sha"] = new StringContextData("abc123def456");
|
||||
jobContext["workflow_repository"] = new StringContextData("explicit-org/explicit-repo");
|
||||
jobContext["workflow_file_path"] = new StringContextData(".github/workflows/explicit.yml");
|
||||
jobRequest.ContextData["job"] = jobContext;
|
||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
|
||||
// Act
|
||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||
|
||||
// Assert: explicit values should be preserved, not overwritten by derivation
|
||||
Assert.Equal("explicit-org/explicit-repo", ec.JobContext.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/explicit.yml", ec.JobContext.WorkflowFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,5 +34,224 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
ctx.CheckRunId = null;
|
||||
Assert.Null(ctx.CheckRunId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowRef_SetAndGet_WorksCorrectly()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "owner/repo/.github/workflows/ci.yml@refs/heads/main";
|
||||
Assert.Equal("owner/repo/.github/workflows/ci.yml@refs/heads/main", ctx.WorkflowRef);
|
||||
Assert.True(ctx.TryGetValue("workflow_ref", out var value));
|
||||
Assert.IsType<StringContextData>(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowRef_NotSet_ReturnsNull()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
Assert.Null(ctx.WorkflowRef);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowRef_SetNull_ClearsValue()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "owner/repo/.github/workflows/ci.yml@refs/heads/main";
|
||||
ctx.WorkflowRef = null;
|
||||
Assert.Null(ctx.WorkflowRef);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowSha_SetAndGet_WorksCorrectly()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowSha = "abc123def456";
|
||||
Assert.Equal("abc123def456", ctx.WorkflowSha);
|
||||
Assert.True(ctx.TryGetValue("workflow_sha", out var value));
|
||||
Assert.IsType<StringContextData>(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowSha_NotSet_ReturnsNull()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
Assert.Null(ctx.WorkflowSha);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowSha_SetNull_ClearsValue()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowSha = "abc123def456";
|
||||
ctx.WorkflowSha = null;
|
||||
Assert.Null(ctx.WorkflowSha);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowRepository_SetAndGet_WorksCorrectly()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRepository = "owner/repo";
|
||||
Assert.Equal("owner/repo", ctx.WorkflowRepository);
|
||||
Assert.True(ctx.TryGetValue("workflow_repository", out var value));
|
||||
Assert.IsType<StringContextData>(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowRepository_NotSet_ReturnsNull()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
Assert.Null(ctx.WorkflowRepository);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowRepository_SetNull_ClearsValue()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRepository = "owner/repo";
|
||||
ctx.WorkflowRepository = null;
|
||||
Assert.Null(ctx.WorkflowRepository);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowFilePath_SetAndGet_WorksCorrectly()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowFilePath = ".github/workflows/ci.yml";
|
||||
Assert.Equal(".github/workflows/ci.yml", ctx.WorkflowFilePath);
|
||||
Assert.True(ctx.TryGetValue("workflow_file_path", out var value));
|
||||
Assert.IsType<StringContextData>(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowFilePath_NotSet_ReturnsNull()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
Assert.Null(ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkflowFilePath_SetNull_ClearsValue()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowFilePath = ".github/workflows/ci.yml";
|
||||
ctx.WorkflowFilePath = null;
|
||||
Assert.Null(ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_PopulatesRepositoryAndFilePath()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "my-org/my-repo/.github/workflows/deploy.yml@refs/heads/main";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Equal("my-org/my-repo", ctx.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/deploy.yml", ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_DoesNotOverwriteExistingValues()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "my-org/my-repo/.github/workflows/deploy.yml@refs/heads/main";
|
||||
ctx.WorkflowRepository = "explicit/override";
|
||||
ctx.WorkflowFilePath = ".github/workflows/override.yml";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Equal("explicit/override", ctx.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/override.yml", ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_NoOp_WhenWorkflowRefIsNull()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Null(ctx.WorkflowRepository);
|
||||
Assert.Null(ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_NoOp_WhenRefHasNoGithubDir()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "some/path/without/github/dir@refs/heads/main";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Null(ctx.WorkflowRepository);
|
||||
Assert.Null(ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_HandlesRefWithoutAtSign()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "my-org/my-repo/.github/workflows/deploy.yml";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Equal("my-org/my-repo", ctx.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/deploy.yml", ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_HandlesDotGithubRepoName()
|
||||
{
|
||||
// Repos can be named ".github" — the marker must be /.github/workflows/ not /.github/
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "octo-org/.github/.github/workflows/ci.yml@refs/heads/main";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Equal("octo-org/.github", ctx.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/ci.yml", ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_TreatsEmptyStringAsUnset()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "my-org/my-repo/.github/workflows/deploy.yml@refs/heads/main";
|
||||
ctx.WorkflowRepository = "";
|
||||
ctx.WorkflowFilePath = "";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Equal("my-org/my-repo", ctx.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/deploy.yml", ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_HandlesPRMergeRef()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "my-org/my-repo/.github/workflows/ci.yml@refs/pull/42/merge";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Equal("my-org/my-repo", ctx.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/ci.yml", ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_HandlesTagRef()
|
||||
{
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "my-org/my-repo/.github/workflows/release.yml@refs/tags/v1.0.0";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Equal("my-org/my-repo", ctx.WorkflowRepository);
|
||||
Assert.Equal(".github/workflows/release.yml", ctx.WorkflowFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeriveWorkflowRefComponents_RejectsInvalidRepoFormat()
|
||||
{
|
||||
// No owner/repo slash — should no-op
|
||||
var ctx = new JobContext();
|
||||
ctx.WorkflowRef = "noslash.github/workflows/ci.yml@refs/heads/main";
|
||||
ctx.DeriveWorkflowRefComponents();
|
||||
|
||||
Assert.Null(ctx.WorkflowRepository);
|
||||
Assert.Null(ctx.WorkflowFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user