Skip to content
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
146 changes: 146 additions & 0 deletions src/Tiger.Tests/AzdoClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System.Net;
using Xunit;

namespace Tiger.Tests;

public class AzdoClientTests
{
[Fact]
public async Task GetBuildsForRepositoryAsync_DefaultsToGitHubRepositoryType()
{
Uri? requestUri = null;
var client = AzdoClient.Create(new DelegateHandler((request, ct) =>
{
requestUri = request.RequestUri;
return Task.FromResult(CreateJsonResponse("""{"count":0,"value":[]}"""));
}));

await client.GetBuildsForRepositoryAsync("dotnet/roslyn");

Assert.NotNull(requestUri);
Assert.Contains("repositoryId=dotnet%2Froslyn", requestUri.ToString());
Assert.Contains("repositoryType=GitHub", requestUri.ToString());
}

[Fact]
public async Task GetBuildsForRepositoryAsync_UsesConfiguredRepositoryType()
{
var requestUris = new List<Uri>();
var client = AzdoClient.Create(new DelegateHandler((request, ct) =>
{
requestUris.Add(request.RequestUri!);
if (request.RequestUri!.ToString().Contains("_apis/git/repositories"))
{
return Task.FromResult(CreateJsonResponse("""
{
"count": 1,
"value": [
{
"id": "11111111-1111-1111-1111-111111111111",
"name": "Repo"
}
]
}
"""));
}

return Task.FromResult(CreateJsonResponse("""
{
"count": 1,
"value": [
{
"id": 42,
"buildNumber": "20250622.1",
"status": "completed",
"result": "succeeded",
"uri": "vstfs:///Build/Build/42",
"sourceBranch": "refs/heads/main",
"sourceVersion": "abc123",
"definition": { "id": 7, "name": "CI" },
"repository": { "id": "Repo", "name": "Repo", "type": "TfsGit" },
"finishTime": "2026-06-22T20:00:00Z"
}
]
}
"""));
}));

var builds = await client.GetBuildsForRepositoryAsync("Repo", repositoryType: AzdoRepositoryTypes.TfsGit);

Assert.Equal(2, requestUris.Count);
Assert.Contains("_apis/git/repositories", requestUris[0].ToString());
Assert.Contains("repositoryId=11111111-1111-1111-1111-111111111111", requestUris[1].ToString());
Assert.Contains("repositoryType=TfsGit", requestUris[1].ToString());
var build = Assert.Single(builds);
Assert.Equal(AzdoRepositoryTypes.TfsGit, build.RepositoryType);
}

[Fact]
public async Task GetCompletedBuildsSinceAsync_UsesConfiguredRepositoryType()
{
var requestUris = new List<Uri>();
var client = AzdoClient.Create(new DelegateHandler((request, ct) =>
{
requestUris.Add(request.RequestUri!);
if (request.RequestUri!.ToString().Contains("_apis/git/repositories"))
{
return Task.FromResult(CreateJsonResponse("""
{
"count": 1,
"value": [
{
"id": "11111111-1111-1111-1111-111111111111",
"name": "Repo"
}
]
}
"""));
}

return Task.FromResult(CreateJsonResponse("""{"count":0,"value":[]}"""));
}));

await client.GetCompletedBuildsSinceAsync(
new DateTime(2026, 6, 22, 0, 0, 0, DateTimeKind.Utc),
repositoryId: "Repo",
repositoryType: AzdoRepositoryTypes.TfsGit);

Assert.Equal(2, requestUris.Count);
Assert.Contains("_apis/git/repositories", requestUris[0].ToString());
Assert.Contains("repositoryId=11111111-1111-1111-1111-111111111111", requestUris[1].ToString());
Assert.Contains("repositoryType=TfsGit", requestUris[1].ToString());
}

[Fact]
public async Task GetBuildsForRepositoryAsync_TfsGitRepositoryId_DoesNotResolveName()
{
Uri? requestUri = null;
var repositoryId = "11111111-1111-1111-1111-111111111111";
var client = AzdoClient.Create(new DelegateHandler((request, ct) =>
{
requestUri = request.RequestUri;
return Task.FromResult(CreateJsonResponse("""{"count":0,"value":[]}"""));
}));

await client.GetBuildsForRepositoryAsync(repositoryId, repositoryType: AzdoRepositoryTypes.TfsGit);

Assert.NotNull(requestUri);
Assert.DoesNotContain("_apis/git/repositories", requestUri.ToString());
Assert.Contains($"repositoryId={repositoryId}", requestUri.ToString());
}

private sealed class DelegateHandler(
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handler) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct) => handler(request, ct);
}

private static HttpResponseMessage CreateJsonResponse(string json)
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"),
};
}
}
62 changes: 62 additions & 0 deletions src/Tiger.Tests/BuildIngestionServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,54 @@ public void InsertBuild_WithPrNumber()
Assert.Equal(99L, prNumber);
}

[Fact]
public void InsertBuild_GitHubPr_CreatesPrInfoTask()
{
var build = new AzdoBuild
{
Id = 4,
BuildNumber = "20250101.4",
DefinitionName = "runtime",
DefinitionId = 42,
Status = "completed",
Result = "succeeded",
Uri = "https://dev.azure.com/org/proj/_build/results?buildId=4",
SourceBranch = "refs/pull/99/merge",
RepositoryName = "dotnet/runtime",
RepositoryType = AzdoRepositoryTypes.GitHub,
PrNumber = 99,
};

_service.InsertBuild("org", "proj", build);

var taskCount = CountIngestionTasks(4, "pr_info");
Assert.Equal(1L, taskCount);
}

[Fact]
public void InsertBuild_AzureReposPr_DoesNotCreatePrInfoTask()
{
var build = new AzdoBuild
{
Id = 5,
BuildNumber = "20250101.5",
DefinitionName = "runtime",
DefinitionId = 42,
Status = "completed",
Result = "succeeded",
Uri = "https://dev.azure.com/org/proj/_build/results?buildId=5",
SourceBranch = "refs/pull/99/merge",
RepositoryName = "runtime",
RepositoryType = AzdoRepositoryTypes.TfsGit,
PrNumber = 99,
};

_service.InsertBuild("org", "proj", build);

var taskCount = CountIngestionTasks(5, "pr_info");
Assert.Equal(0L, taskCount);
}

[Fact]
public void InsertBuild_Duplicate_IsIgnored()
{
Expand Down Expand Up @@ -327,4 +375,18 @@ private void InsertSampleBuild(int buildId)
});
}

private long CountIngestionTasks(int buildId, string taskType)
{
return _db.WithCommand(cmd =>
{
cmd.CommandText = """
SELECT COUNT(*) FROM build_ingestion_tasks
WHERE organization = 'org' AND build_id = @buildId AND task_type = @taskType
""";
cmd.Parameters.AddWithValue("@buildId", buildId);
cmd.Parameters.AddWithValue("@taskType", taskType);
return (long)cmd.ExecuteScalar()!;
});
}

}
85 changes: 85 additions & 0 deletions src/Tiger.Tests/BuildPollerTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Net;
using Xunit;

namespace Tiger.Tests;
Expand Down Expand Up @@ -126,6 +127,75 @@ public void FilterNewBuilds_LongRunningBuildNotMissed()
Assert.Equal(150, result[0].Id);
}

[Fact]
public async Task PollSourceAsync_UsesConfiguredRepositoryType()
{
var requestUris = new List<Uri>();
var source = new AzdoSource
{
Organization = "org",
Project = "proj",
RepositoryType = AzdoRepositoryTypes.TfsGit,
Repositories = ["Repo"],
};
var config = new TigerConfig
{
Sources = [source],
};
var handler = new DelegateHandler((request, ct) =>
{
requestUris.Add(request.RequestUri!);
if (request.RequestUri!.ToString().Contains("_apis/git/repositories"))
{
return Task.FromResult(CreateJsonResponse("""
{
"count": 1,
"value": [
{
"id": "11111111-1111-1111-1111-111111111111",
"name": "Repo"
}
]
}
"""));
}

return Task.FromResult(CreateJsonResponse("""
{
"count": 1,
"value": [
{
"id": 42,
"buildNumber": "20250622.1",
"status": "completed",
"result": "succeeded",
"uri": "vstfs:///Build/Build/42",
"sourceBranch": "refs/heads/main",
"definition": { "id": 7, "name": "CI" },
"repository": { "id": "Repo", "name": "Repo", "type": "TfsGit" }
}
]
}
"""));
});
var poller = new BuildPoller(config, _db, new AzdoClientFactory((org, proj) => AzdoClient.Create(handler, org, proj)));
List<AzdoBuild>? observedBuilds = null;
poller.OnNewBuilds = (_, _, builds) =>
{
observedBuilds = builds;
return Task.CompletedTask;
};

await poller.PollSourceAsync(source, CancellationToken.None);

Assert.Equal(2, requestUris.Count);
Assert.Contains("_apis/git/repositories", requestUris[0].ToString());
Assert.Contains("repositoryId=11111111-1111-1111-1111-111111111111", requestUris[1].ToString());
Assert.Contains("repositoryType=TfsGit", requestUris[1].ToString());
var build = Assert.Single(observedBuilds!);
Assert.Equal(AzdoRepositoryTypes.TfsGit, build.RepositoryType);
}

private BuildPoller CreatePoller()
{
var config = new TigerConfig { Sources = [] };
Expand All @@ -143,4 +213,19 @@ private BuildPoller CreatePoller()
DefinitionName = "test-def",
FinishTime = finishTime,
};

private sealed class DelegateHandler(
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handler) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct) => handler(request, ct);
}

private static HttpResponseMessage CreateJsonResponse(string json)
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"),
};
}
}
Loading
Loading