feat: Implement dependency review and dependency submission APIs (#2932)

Implement dependency review and dependency submission

Co-authored-by: André Pereira <Andre.LuisPereira@Student.HTW-Berlin.de>
This commit is contained in:
awedist
2024-06-15 00:03:11 +02:00
committed by GitHub
parent 6c43183837
commit 7d54cb0d85
39 changed files with 1949 additions and 8 deletions

View File

@@ -0,0 +1,22 @@
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Dependency Graph API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph">Git Dependency Graph API documentation</a> for more information.
/// </remarks>
public interface IObservableDependencyGraphClient
{
/// <summary>
/// Client for getting a dependency comparison between two commits.
/// </summary>
IObservableDependencyReviewClient DependencyReview { get; }
/// <summary>
/// Client for submitting dependency snapshots.
/// </summary>
IObservableDependencySubmissionClient DependencySubmission { get; }
}
}

View File

@@ -0,0 +1,39 @@

using System;
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Dependency Review API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">Git Dependency Review API documentation</a> for more information.
/// </remarks>
public interface IObservableDependencyReviewClient
{
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
IObservable<DependencyDiff> GetAll(string owner, string name, string @base, string head);
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
IObservable<DependencyDiff> GetAll(long repositoryId, string @base, string head);
}
}

View File

@@ -0,0 +1,39 @@
using System;
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Dependency Submission API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">Dependency Submission API documentation</a> for more details.
/// </remarks>
public interface IObservableDependencySubmissionClient
{
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The repository's owner</param>
/// <param name="name">The repository's name</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
IObservable<DependencySnapshotSubmission> Create(string owner, string name, NewDependencySnapshot snapshot);
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
IObservable<DependencySnapshotSubmission> Create(long repositoryId, NewDependencySnapshot snapshot);
}
}

View File

@@ -0,0 +1,29 @@
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Dependency Graph API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph">Git Dependency Graph API documentation</a> for more information.
/// </remarks>
public class ObservableDependencyGraphClient : IObservableDependencyGraphClient
{
public ObservableDependencyGraphClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, nameof(client));
//DependencyReview = new ObservableDependencyReviewClient(client);
DependencySubmission = new ObservableDependencySubmissionClient(client);
}
/// <summary>
/// Client for getting a dependency comparison between two commits.
/// </summary>
public IObservableDependencyReviewClient DependencyReview { get; private set; }
/// <summary>
/// Client for submitting dependency snapshots.
/// </summary>
public IObservableDependencySubmissionClient DependencySubmission { get; private set; }
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Dependency Review API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">Git Dependency Review API documentation</a> for more information.
/// </remarks>
public class ObservableDependencyReviewClient : IObservableDependencyReviewClient
{
readonly IDependencyReviewClient _client;
public ObservableDependencyReviewClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, nameof(client));
_client = client.DependencyGraph.DependencyReview;
}
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
public IObservable<DependencyDiff> GetAll(string owner, string name, string @base, string head)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(name, nameof(name));
Ensure.ArgumentNotNullOrEmptyString(@base, nameof(@base));
Ensure.ArgumentNotNullOrEmptyString(head, nameof(head));
return _client.GetAll(owner, name, @base, head).ToObservable().SelectMany(x => x);
}
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
public IObservable<DependencyDiff> GetAll(long repositoryId, string @base, string head)
{
Ensure.ArgumentNotNullOrEmptyString(@base, nameof(@base));
Ensure.ArgumentNotNullOrEmptyString(head, nameof(head));
return _client.GetAll(repositoryId, @base, head).ToObservable().SelectMany(x => x);
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Reactive.Threading.Tasks;
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Dependency Submission API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">Dependency Submission API documentation</a> for more details.
/// </remarks>
public class ObservableDependencySubmissionClient : IObservableDependencySubmissionClient
{
readonly IDependencySubmissionClient _client;
public ObservableDependencySubmissionClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, nameof(client));
_client = client.DependencyGraph.DependencySubmission;
}
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The repository's owner</param>
/// <param name="name">The repository's name</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
public IObservable<DependencySnapshotSubmission> Create(string owner, string name, NewDependencySnapshot snapshot)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(name, nameof(name));
Ensure.ArgumentNotNull(snapshot, nameof(snapshot));
return _client.Create(owner, name, snapshot).ToObservable();
}
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
public IObservable<DependencySnapshotSubmission> Create(long repositoryId, NewDependencySnapshot snapshot)
{
Ensure.ArgumentNotNull(snapshot, nameof(snapshot));
return _client.Create(repositoryId, snapshot).ToObservable();
}
}
}

View File

@@ -44,5 +44,6 @@ namespace Octokit.Reactive
IObservableActionsClient Actions { get; } IObservableActionsClient Actions { get; }
IObservableCodespacesClient Codespaces { get; } IObservableCodespacesClient Codespaces { get; }
IObservableCopilotClient Copilot { get; } IObservableCopilotClient Copilot { get; }
IObservableDependencyGraphClient DependencyGraph { get; }
} }
} }

View File

@@ -59,6 +59,7 @@ namespace Octokit.Reactive
Actions = new ObservableActionsClient(gitHubClient); Actions = new ObservableActionsClient(gitHubClient);
Codespaces = new ObservableCodespacesClient(gitHubClient); Codespaces = new ObservableCodespacesClient(gitHubClient);
Copilot = new ObservableCopilotClient(gitHubClient); Copilot = new ObservableCopilotClient(gitHubClient);
DependencyGraph = new ObservableDependencyGraphClient(gitHubClient);
} }
public IConnection Connection public IConnection Connection
@@ -108,7 +109,8 @@ namespace Octokit.Reactive
public IObservableActionsClient Actions { get; private set; } public IObservableActionsClient Actions { get; private set; }
public IObservableCodespacesClient Codespaces { get; private set; } public IObservableCodespacesClient Codespaces { get; private set; }
public IObservableCopilotClient Copilot { get; set; } public IObservableCopilotClient Copilot { get; set; }
public IObservableDependencyGraphClient DependencyGraph { get; }
/// <summary> /// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made /// Gets the latest API Info - this will be null if no API calls have been made
/// </summary> /// </summary>

View File

@@ -0,0 +1,55 @@
using Octokit;
using Octokit.Tests.Integration;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
/// <summary>
/// Base and head must have different dependencies
/// </summary>
public class DependencyReviewClientTests
{
public class TheGetAllMethod
{
readonly IDependencyReviewClient _fixture;
readonly IGitHubClient _github;
readonly string _owner;
readonly string _repo;
readonly string _base;
readonly string _head;
readonly long _repoId;
public TheGetAllMethod()
{
_github = Helper.GetAuthenticatedClient();
_fixture = _github.DependencyGraph.DependencyReview;
_owner = "octokit";
_repo = "octokit.net";
_base = "main";
_head = "brave-new-codegen-world";
_repoId = _github.Repository.Get(_owner, _repo).Result.Id;
}
[IntegrationTest]
public async Task GetDependencyDiffs()
{
var diffs = await _fixture.GetAll(_owner, _repo, _base, _head);
Assert.NotEmpty(diffs);
Assert.NotNull(diffs);
Assert.IsType<DependencyDiff>(diffs.First());
}
[IntegrationTest]
public async Task GetDependencyDiffsWithRepositoryId()
{
var diffs = await _fixture.GetAll(_repoId, _base, _head);
Assert.NotEmpty(diffs);
Assert.NotNull(diffs);
Assert.IsType<DependencyDiff>(diffs.First());
}
}
}

View File

@@ -0,0 +1,114 @@
using Octokit;
using Octokit.Tests.Integration;
using Octokit.Tests.Integration.Helpers;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Xunit;
public class DependencySubmissionClientTests
{
public class TheCreateMethod
{
private readonly IDependencySubmissionClient _dependencySubmissionClient;
private readonly RepositoryContext _context;
private readonly NewDependencySnapshot _newDependencySnapshot;
public TheCreateMethod()
{
var github = Helper.GetAuthenticatedClient();
_dependencySubmissionClient = github.DependencyGraph.DependencySubmission;
_context = github.CreateRepositoryContextWithAutoInit("public-repo").Result;
var job = new NewDependencySnapshotJob("runid", "example-correlator");
var detector = new NewDependencySnapshotDetector("example-detector", "1.0.0", "https://github.com/example/detector");
var resolvedMetadata = new Dictionary<string, object>
{
{ "License", "MIT" }
};
var manifests = new Dictionary<string, NewDependencySnapshotManifest>
{
{
"package-lock.json",
new NewDependencySnapshotManifest("package-lock.json")
{
File = new NewDependencySnapshotManifestFile
{
SourceLocation = "src/package-lock.json"
},
Resolved = new Dictionary<string, NewDependencySnapshotResolvedDependency>
{
{
"@actions/core",
new NewDependencySnapshotResolvedDependency
{
PackageUrl = "pkg:/npm/%40actions/core@1.1.9",
Dependencies = new Collection<string> { "@actions/http-client" },
Scope = ResolvedPackageKeyScope.Runtime,
Relationship = ResolvedPackageKeyRelationship.Indirect,
Metadata = resolvedMetadata
}
},
{
"@actions/http-client",
new NewDependencySnapshotResolvedDependency
{
PackageUrl = "pkg:/npm/%40actions/http-client@1.0.7",
Scope = ResolvedPackageKeyScope.Development,
Relationship = ResolvedPackageKeyRelationship.Direct
}
},
{
"tunnel",
new NewDependencySnapshotResolvedDependency
{
PackageUrl = "pkg:/npm/tunnel@0.0.6",
Dependencies = new Collection<string>(),
Relationship = ResolvedPackageKeyRelationship.Direct,
}
}
}
}
}
};
var snapshotMetadata = new Dictionary<string, object>
{
{ "Author", "John Doe" },
{ "Version", "1.0.0" },
{ "License", "MIT" }
};
_newDependencySnapshot = new NewDependencySnapshot(
1,
"ce587453ced02b1526dfb4cb910479d431683101",
"refs/heads/main",
"2022-06-14T20:25:00Z",
job,
detector)
{
Metadata = snapshotMetadata,
Manifests = manifests
};
}
[IntegrationTest]
public async Task CanCreateDependencySnapshot()
{
var submission = await _dependencySubmissionClient.Create(_context.RepositoryOwner, _context.RepositoryName, _newDependencySnapshot);
Assert.Equal(DependencySnapshotSubmissionResult.Accepted, submission.Result);
}
[IntegrationTest]
public async Task CanCreateDependencySnapshotWithRepositoryId()
{
var submission = await _dependencySubmissionClient.Create(_context.Repository.Id, _newDependencySnapshot);
Assert.Equal(DependencySnapshotSubmissionResult.Accepted, submission.Result);
}
}
}

View File

@@ -0,0 +1,51 @@
using Octokit;
using Octokit.Reactive;
using Octokit.Tests.Integration;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Xunit;
/// <summary>
/// Base and head must have different dependencies
/// </summary>
public class ObservableDependencyReviewClientTests
{
public class TheGetAllMethod
{
readonly ObservableDependencyReviewClient _DependencyReviewClient;
readonly string owner = "octokit";
readonly string repo = "octokit.net";
readonly string @base = "main";
readonly string head = "brave-new-codegen-world";
readonly long repoId;
public TheGetAllMethod()
{
var github = Helper.GetAuthenticatedClient();
_DependencyReviewClient = new ObservableDependencyReviewClient(github);
repoId = github.Repository.Get(owner, repo).Result.Id;
}
[IntegrationTest]
public async Task GetDependencyDiffs()
{
var diffs = await _DependencyReviewClient.GetAll(owner, repo, @base, head).ToList();
Assert.NotEmpty(diffs);
Assert.NotNull(diffs);
Assert.IsType<DependencyDiff>(diffs.First());
}
[IntegrationTest]
public async Task GetDependencyDiffsWithRepositoryId()
{
var diffs = await _DependencyReviewClient.GetAll(repoId, @base, head).ToList();
Assert.NotEmpty(diffs);
Assert.NotNull(diffs);
Assert.IsType<DependencyDiff>(diffs.First());
}
}
}

View File

@@ -0,0 +1,115 @@
using Octokit;
using Octokit.Reactive;
using Octokit.Tests.Integration;
using Octokit.Tests.Integration.Helpers;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Xunit;
public class ObservableDependencySubmissionClientTests
{
public class TheCreateMethod
{
private readonly ObservableGitHubClient _client;
private readonly RepositoryContext _context;
private readonly NewDependencySnapshot _newDependencySnapshot;
public TheCreateMethod()
{
var github = Helper.GetAuthenticatedClient();
_client = new ObservableGitHubClient(github);
_context = github.CreateRepositoryContextWithAutoInit("public-repo").Result;
var job = new NewDependencySnapshotJob("runid", "example-correlator");
var detector = new NewDependencySnapshotDetector("example-detector", "1.0.0", "https://github.com/example/detector");
var resolvedMetadata = new Dictionary<string, object>
{
{ "License", "MIT" }
};
var manifests = new Dictionary<string, NewDependencySnapshotManifest>
{
{
"package-lock.json",
new NewDependencySnapshotManifest("package-lock.json")
{
File = new NewDependencySnapshotManifestFile
{
SourceLocation = "src/package-lock.json"
},
Resolved = new Dictionary<string, NewDependencySnapshotResolvedDependency>
{
{
"@actions/core",
new NewDependencySnapshotResolvedDependency
{
PackageUrl = "pkg:/npm/%40actions/core@1.1.9",
Dependencies = new Collection<string> { "@actions/http-client" },
Scope = ResolvedPackageKeyScope.Runtime,
Relationship = ResolvedPackageKeyRelationship.Indirect,
Metadata = resolvedMetadata
}
},
{
"@actions/http-client",
new NewDependencySnapshotResolvedDependency
{
PackageUrl = "pkg:/npm/%40actions/http-client@1.0.7",
Scope = ResolvedPackageKeyScope.Development,
Relationship = ResolvedPackageKeyRelationship.Direct
}
},
{
"tunnel",
new NewDependencySnapshotResolvedDependency
{
PackageUrl = "pkg:/npm/tunnel@0.0.6",
Dependencies = new Collection<string>(),
Relationship = ResolvedPackageKeyRelationship.Direct,
}
}
}
}
}
};
var snapshotMetadata = new Dictionary<string, object>
{
{ "Author", "John Doe" },
{ "Version", "1.0.0" },
{ "License", "MIT" }
};
_newDependencySnapshot = new NewDependencySnapshot(
1,
"ce587453ced02b1526dfb4cb910479d431683101",
"refs/heads/main",
"2022-06-14T20:25:00Z",
job,
detector)
{
Metadata = snapshotMetadata,
Manifests = manifests
};
}
[IntegrationTest]
public async Task CanCreateDependencySnapshot()
{
var submission = await _client.DependencyGraph.DependencySubmission.Create(_context.RepositoryOwner, _context.RepositoryName, _newDependencySnapshot);
Assert.Equal(DependencySnapshotSubmissionResult.Accepted, submission.Result);
}
[IntegrationTest]
public async Task CanCreateDependencySnapshotWithRepositoryId()
{
var submission = await _client.DependencyGraph.DependencySubmission.Create(_context.Repository.Id, _newDependencySnapshot);
Assert.Equal(DependencySnapshotSubmissionResult.Accepted, submission.Result);
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using Xunit;
namespace Octokit
{
public class DependencyGraphClientTests
{
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new DependencyGraphClient(null));
}
}
}
}

View File

@@ -0,0 +1,74 @@
using NSubstitute;
using Octokit.Tests;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Octokit
{
public class DependencyReviewClientTests
{
public class TheGetAllMethod
{
[Fact]
public async Task RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new DependencyReviewClient(connection);
await client.GetAll("fake", "repo", "base", "head");
connection.Received().GetAll<DependencyDiff>(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/dependency-graph/compare/base...head"));
}
[Fact]
public async Task RequestsCorrectUrlWithRepositoryId()
{
var connection = Substitute.For<IApiConnection>();
var client = new DependencyReviewClient(connection);
await client.GetAll(1, "base", "head");
connection.Received().GetAll<DependencyDiff>(Arg.Is<Uri>(u => u.ToString() == "repositories/1/dependency-graph/compare/base...head"));
}
[Fact]
public async Task EnsuresNonNullArguments()
{
var client = new DependencyReviewClient(Substitute.For<IApiConnection>());
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAll(null, "name", "base", "head"));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAll("owner", null, "base", "head"));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAll("owner", "name", null, "head"));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAll("owner", "name", "base", null));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAll(1, null, "head"));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAll(1, "base", null));
}
[Fact]
public async Task EnsuresNonEmptyArguments()
{
var client = new DependencyReviewClient(Substitute.For<IApiConnection>());
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAll("", "name", "base", "head"));
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAll("owner", "", "base", "head"));
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAll("owner", "name", "", "head"));
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAll("owner", "name", "head", ""));
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAll(1, "", "head"));
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAll(1, "base", ""));
}
}
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new DependencyReviewClient(null));
}
}
}
}

View File

@@ -0,0 +1,73 @@
using NSubstitute;
using System;
using System.Threading.Tasks;
using Xunit;
namespace Octokit
{
public class DependencySubmissionClientTests
{
public class TheCreateMethod
{
private NewDependencySnapshot newDependencySnapshot = new NewDependencySnapshot(
1,
"sha",
"ref",
"scanned",
new NewDependencySnapshotJob("runId", "jobCorrelator"),
new NewDependencySnapshotDetector("detectorName", "detectorVersion", "detectorUrl"));
[Fact]
public void PostsToTheCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new DependencySubmissionClient(connection);
var expectedUrl = "repos/owner/name/dependency-graph/snapshots";
client.Create("owner", "name", newDependencySnapshot);
connection.Received(1).Post<DependencySnapshotSubmission>(Arg.Is<Uri>(u => u.ToString() == expectedUrl), Arg.Any<JsonObject>());
}
[Fact]
public void PostsToTheCorrectUrlWithRepositoryId()
{
var connection = Substitute.For<IApiConnection>();
var client = new DependencySubmissionClient(connection);
var expectedUrl = "repositories/1/dependency-graph/snapshots";
client.Create(1, newDependencySnapshot);
connection.Received(1).Post<DependencySnapshotSubmission>(Arg.Is<Uri>(u => u.ToString() == expectedUrl), Arg.Any<JsonObject>());
}
[Fact]
public async Task EnsuresNonNullArguments()
{
var client = new DependencySubmissionClient(Substitute.For<IApiConnection>());
await Assert.ThrowsAsync<ArgumentNullException>(() => client.Create(null, "name", newDependencySnapshot));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.Create("owner", null, newDependencySnapshot));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.Create("owner", "name", null));
}
[Fact]
public async Task EnsuresNonEmptyArguments()
{
var client = new DependencySubmissionClient(Substitute.For<IApiConnection>());
await Assert.ThrowsAsync<ArgumentException>(() => client.Create("", "name", newDependencySnapshot));
await Assert.ThrowsAsync<ArgumentException>(() => client.Create("owner", "", newDependencySnapshot));
}
}
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new DependencySubmissionClient(null));
}
}
}
}

View File

@@ -0,0 +1,56 @@
using Octokit.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Octokit
{
public class DependencyDiffTests
{
[Fact]
public void CanDeserialize()
{
const string json = @"{
""change_type"": ""removed"",
""manifest"": ""Cargo.lock"",
""ecosystem"": ""cargo"",
""name"": ""libsqlite3-sys"",
""version"": ""0.22.2"",
""package_url"": ""pkg:cargo/libsqlite3-sys@0.22.2"",
""license"": ""MIT"",
""source_repository_url"": ""https://github.com/rusqlite/rusqlite"",
""scope"": ""runtime"",
""vulnerabilities"": [
{
""severity"": ""high"",
""advisory_ghsa_id"": ""GHSA-jw36-hf63-69r9"",
""advisory_summary"": ""`libsqlite3-sys` via C SQLite improperly validates array index"",
""advisory_url"": ""https://github.com/advisories/GHSA-jw36-hf63-69r9""
}
]
}";
var actual = new SimpleJsonSerializer().Deserialize<DependencyDiff>(json);
Assert.Equal("removed", actual.ChangeType);
Assert.Equal("Cargo.lock", actual.Manifest);
Assert.Equal("cargo", actual.Ecosystem);
Assert.Equal("libsqlite3-sys", actual.Name);
Assert.Equal("0.22.2", actual.Version);
Assert.Equal("pkg:cargo/libsqlite3-sys@0.22.2", actual.PackageUrl);
Assert.Equal("MIT", actual.License);
Assert.Equal("https://github.com/rusqlite/rusqlite", actual.SourceRepositoryUrl);
Assert.Equal("runtime", actual.Scope);
Assert.NotNull(actual.Vulnerabilities);
Assert.Single(actual.Vulnerabilities);
var vulnerability = actual.Vulnerabilities.First();
Assert.Equal("high", vulnerability.Severity);
Assert.Equal("GHSA-jw36-hf63-69r9", vulnerability.AdvisoryGhsaId);
Assert.Equal("`libsqlite3-sys` via C SQLite improperly validates array index", vulnerability.AdvisorySummary);
Assert.Equal("https://github.com/advisories/GHSA-jw36-hf63-69r9", vulnerability.AdvisoryUrl);
}
}
}

View File

@@ -0,0 +1,32 @@
using Octokit.Internal;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xunit;
namespace Octokit
{
public class DependencySnapshotSubmissionTests
{
[Fact]
public void CanDeserialize()
{
const string json = @"
{
""id"": 12345,
""created_at"": ""2018-05-04T01:14:52Z"",
""message"": ""Dependency results for the repo have been successfully updated."",
""result"": ""SUCCESS""
}";
var serializer = new SimpleJsonSerializer();
var actual = serializer.Deserialize<DependencySnapshotSubmission>(json);
Assert.Equal(12345, actual.Id);
Assert.Equal(DateTimeOffset.Parse("2018-05-04T01:14:52Z"), actual.CreatedAt);
Assert.Equal("Dependency results for the repo have been successfully updated.", actual.Message);
Assert.Equal("SUCCESS", actual.Result);
}
}
}

View File

@@ -0,0 +1,18 @@
using Octokit.Reactive;
using System;
using Xunit;
namespace Octokit.Tests.Reactive
{
public class ObservableDependencyGraphClientTests
{
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new ObservableDependencyGraphClient(null));
}
}
}
}

View File

@@ -0,0 +1,72 @@
using NSubstitute;
using Octokit.Reactive;
using System;
using System.Threading.Tasks;
using Xunit;
namespace Octokit.Tests.Reactive
{
public class ObservableDependencyReviewClientTests
{
public class TheGetAllMethod
{
[Fact]
public async Task RequestsCorrectUrl()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableDependencyReviewClient(gitHubClient);
client.GetAll("fake", "repo", "base", "head");
gitHubClient.Received().DependencyGraph.DependencyReview.GetAll("fake", "repo", "base", "head");
}
[Fact]
public void RequestsCorrectUrlWithRepositoryId()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableDependencyReviewClient(gitHubClient);
client.GetAll(1, "base", "head");
gitHubClient.Received().DependencyGraph.DependencyReview.GetAll(1, "base", "head");
}
[Fact]
public void EnsuresNonNullArguments()
{
var client = new ObservableDependencyReviewClient(Substitute.For<IGitHubClient>());
Assert.Throws<ArgumentNullException>(() => client.GetAll(null, "name", "base", "head"));
Assert.Throws<ArgumentNullException>(() => client.GetAll("owner", null, "base", "head"));
Assert.Throws<ArgumentNullException>(() => client.GetAll("owner", "name", null, "head"));
Assert.Throws<ArgumentNullException>(() => client.GetAll("owner", "name", "base", null));
Assert.Throws<ArgumentNullException>(() => client.GetAll(1, null, "head"));
Assert.Throws<ArgumentNullException>(() => client.GetAll(1, "base", null));
}
[Fact]
public void EnsuresNonEmptyArguments()
{
var client = new ObservableDependencyReviewClient(Substitute.For<IGitHubClient>());
Assert.Throws<ArgumentException>(() => client.GetAll("", "name", "base", "head"));
Assert.Throws<ArgumentException>(() => client.GetAll("owner", "", "base", "head"));
Assert.Throws<ArgumentException>(() => client.GetAll("owner", "name", "", "head"));
Assert.Throws<ArgumentException>(() => client.GetAll("owner", "name", "base", ""));
Assert.Throws<ArgumentException>(() => client.GetAll(1, "", "head"));
Assert.Throws<ArgumentException>(() => client.GetAll(1, "base", ""));
}
}
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new ObservableDependencyReviewClient(null));
}
}
}
}

View File

@@ -0,0 +1,71 @@
using NSubstitute;
using Octokit.Reactive;
using System;
using Xunit;
namespace Octokit.Tests.Reactive
{
public class ObservableDependencySubmissionClientTests
{
public class TheCreateMethod
{
private NewDependencySnapshot newDependencySnapshot = new NewDependencySnapshot(
1,
"sha",
"ref",
"scanned",
new NewDependencySnapshotJob("runId", "jobCorrelator"),
new NewDependencySnapshotDetector("detectorName", "detectorVersion", "detectorUrl"));
[Fact]
public void PostsToTheCorrectUrl()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableDependencySubmissionClient(gitHubClient);
client.Create("fake", "repo", newDependencySnapshot);
gitHubClient.DependencyGraph.DependencySubmission.Received().Create("fake", "repo", newDependencySnapshot);
}
[Fact]
public void PostsToTheCorrectUrlWithRepositoryId()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableDependencySubmissionClient(gitHubClient);
client.Create(1, newDependencySnapshot);
gitHubClient.DependencyGraph.DependencySubmission.Received().Create(1, newDependencySnapshot);
}
[Fact]
public void EnsuresNonNullArguments()
{
var client = new ObservableDependencySubmissionClient(Substitute.For<IGitHubClient>());
Assert.Throws<ArgumentNullException>(() => client.Create(null, "name", newDependencySnapshot));
Assert.Throws<ArgumentNullException>(() => client.Create("owner", null, newDependencySnapshot));
Assert.Throws<ArgumentNullException>(() => client.Create("owner", "name", null));
}
[Fact]
public void EnsuresNonEmptyArguments()
{
var client = new ObservableDependencySubmissionClient(Substitute.For<IGitHubClient>());
Assert.Throws<ArgumentException>(() => client.Create("", "name", newDependencySnapshot));
Assert.Throws<ArgumentException>(() => client.Create("owner", "", newDependencySnapshot));
}
}
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new ObservableDependencySubmissionClient(null));
}
}
}
}

View File

@@ -0,0 +1,31 @@
namespace Octokit
{
/// <summary>
/// A client for GitHub's Dependency Graph API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph">Git Dependency Graph API documentation</a> for more information.
/// </remarks>
public class DependencyGraphClient : IDependencyGraphClient
{
/// <summary>
/// Instantiates a new GitHub Dependency Graph API client.
/// </summary>
/// <param name="apiConnection">An API connection</param>
public DependencyGraphClient(IApiConnection apiConnection)
{
DependencyReview = new DependencyReviewClient(apiConnection);
DependencySubmission = new DependencySubmissionClient(apiConnection);
}
/// <summary>
/// Client for getting a dependency comparison between two commits.
/// </summary>
public IDependencyReviewClient DependencyReview { get; private set; }
/// <summary>
/// Client for submitting dependency snapshots.
/// </summary>
public IDependencySubmissionClient DependencySubmission { get; private set; }
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Dependency Review API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">Git Dependency Review API documentation</a> for more information.
/// </remarks>
public class DependencyReviewClient : ApiClient, IDependencyReviewClient
{
/// <summary>
/// Instantiate a new GitHub Dependency Review API client.
/// </summary>
/// <param name="apiConnection">An API connection</param>
public DependencyReviewClient(IApiConnection apiConnection) : base(apiConnection)
{
}
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
[ManualRoute("GET", "/repos/{owner}/{repo}/dependency-graph/compare/{base}...{head}")]
public Task<IReadOnlyList<DependencyDiff>> GetAll(string owner, string name, string @base, string head)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(name, nameof(name));
Ensure.ArgumentNotNullOrEmptyString(@base, nameof(@base));
Ensure.ArgumentNotNullOrEmptyString(head, nameof(head));
return ApiConnection.GetAll<DependencyDiff>(ApiUrls.DependencyReview(owner, name, @base, head));
}
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
[ManualRoute("GET", "/repositories/{id}/dependency-graph/compare/{base}...{head}")]
public Task<IReadOnlyList<DependencyDiff>> GetAll(long repositoryId, string @base, string head)
{
Ensure.ArgumentNotNullOrEmptyString(@base, nameof(@base));
Ensure.ArgumentNotNullOrEmptyString(head, nameof(head));
return ApiConnection.GetAll<DependencyDiff>(ApiUrls.DependencyReview(repositoryId, @base, head));
}
}
}

View File

@@ -0,0 +1,127 @@
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Dependency Submission API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">Dependency Submission API documentation</a> for more details.
/// </remarks>
public class DependencySubmissionClient : ApiClient, IDependencySubmissionClient
{
/// <summary>
/// Initializes a new GitHub Dependency Submission API client.
/// </summary>
/// <param name="apiConnection">An API connection</param>
public DependencySubmissionClient(IApiConnection apiConnection) : base(apiConnection) { }
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The repository's owner</param>
/// <param name="name">The repository's name</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
[ManualRoute("POST", "/repos/{owner}/{repo}/dependency-graph/snapshots")]
public Task<DependencySnapshotSubmission> Create(string owner, string name, NewDependencySnapshot snapshot)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(name, nameof(name));
Ensure.ArgumentNotNull(snapshot, nameof(snapshot));
var newDependencySnapshotAsObject = ConvertToJsonObject(snapshot);
return ApiConnection.Post<DependencySnapshotSubmission>(ApiUrls.DependencySubmission(owner, name), newDependencySnapshotAsObject);
}
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
[ManualRoute("POST", "/repositories/{id}/dependency-graph/snapshots")]
public Task<DependencySnapshotSubmission> Create(long repositoryId, NewDependencySnapshot snapshot)
{
Ensure.ArgumentNotNull(snapshot, nameof(snapshot));
var newDependencySnapshotAsObject = ConvertToJsonObject(snapshot);
return ApiConnection.Post<DependencySnapshotSubmission>(ApiUrls.DependencySubmission(repositoryId), newDependencySnapshotAsObject);
}
/// <summary>
/// Dependency snapshots dictionaries such as Manifests need to be passed as JsonObject in order to be serialized correctly
/// </summary>
private JsonObject ConvertToJsonObject(NewDependencySnapshot snapshot)
{
var newSnapshotAsObject = new JsonObject();
newSnapshotAsObject.Add("version", snapshot.Version);
newSnapshotAsObject.Add("sha", snapshot.Sha);
newSnapshotAsObject.Add("ref", snapshot.Ref);
newSnapshotAsObject.Add("scanned", snapshot.Scanned);
newSnapshotAsObject.Add("job", snapshot.Job);
newSnapshotAsObject.Add("detector", snapshot.Detector);
if (snapshot.Metadata != null)
{
var metadataAsObject = new JsonObject();
foreach (var kvp in snapshot.Metadata)
{
metadataAsObject.Add(kvp.Key, kvp.Value);
}
newSnapshotAsObject.Add("metadata", metadataAsObject);
}
if (snapshot.Manifests != null)
{
var manifestsAsObject = new JsonObject();
foreach (var manifestKvp in snapshot.Manifests)
{
var manifest = manifestKvp.Value;
var manifestAsObject = new JsonObject();
manifestAsObject.Add("name", manifest.Name);
if (manifest.File.SourceLocation != null)
{
var manifestFileAsObject = new { SourceLocation = manifest.File.SourceLocation };
manifestAsObject.Add("file", manifestFileAsObject);
}
if (manifest.Metadata != null)
{
manifestAsObject.Add("metadata", manifest.Metadata);
}
if (manifest.Resolved != null)
{
var resolvedAsObject = new JsonObject();
foreach (var resolvedKvp in manifest.Resolved)
{
resolvedAsObject.Add(resolvedKvp.Key, resolvedKvp.Value);
}
manifestAsObject.Add("resolved", resolvedAsObject);
}
manifestsAsObject.Add(manifestKvp.Key, manifestAsObject);
}
newSnapshotAsObject.Add("manifests", manifestsAsObject);
}
return newSnapshotAsObject;
}
}
}

View File

@@ -0,0 +1,21 @@
namespace Octokit
{
/// <summary>
/// A client for GitHub's Dependency Graph API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph">Git Dependency Graph API documentation</a> for more information.
/// </remarks>
public interface IDependencyGraphClient
{
/// <summary>
/// Client for getting a dependency comparison between two commits.
/// </summary>
IDependencyReviewClient DependencyReview { get; }
/// <summary>
/// Client for submitting dependency snapshots.
/// </summary>
IDependencySubmissionClient DependencySubmission { get; }
}
}

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Dependency Review API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">Git Dependency Review API documentation</a> for more information.
/// </remarks>
public interface IDependencyReviewClient
{
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
[ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API.")]
Task<IReadOnlyList<DependencyDiff>> GetAll(string owner, string name, string @base, string head);
/// <summary>
/// Gets all <see cref="DependencyDiff"/>s for the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/dependency-graph/dependency-review">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
[ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API.")]
Task<IReadOnlyList<DependencyDiff>> GetAll(long repositoryId, string @base, string head);
}
}

View File

@@ -0,0 +1,38 @@
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Dependency Submission API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">Dependency Submission API documentation</a> for more details.
/// </remarks>
public interface IDependencySubmissionClient
{
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The repository's owner</param>
/// <param name="name">The repository's name</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
Task<DependencySnapshotSubmission> Create(string owner, string name, NewDependencySnapshot snapshot);
/// <summary>
/// Creates a new dependency snapshot.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/rest/dependency-graph/dependency-submission">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="snapshot">The dependency snapshot to create</param>
/// <exception cref="ApiException">Thrown when a general API error occurs</exception>
/// <returns>A <see cref="DependencySnapshotSubmission"/> instance for the created snapshot</returns>
Task<DependencySnapshotSubmission> Create(long repositoryId, NewDependencySnapshot snapshot);
}
}

View File

@@ -122,6 +122,7 @@ namespace Octokit
Actions = new ActionsClient(apiConnection); Actions = new ActionsClient(apiConnection);
Codespaces = new CodespacesClient(apiConnection); Codespaces = new CodespacesClient(apiConnection);
Copilot = new CopilotClient(apiConnection); Copilot = new CopilotClient(apiConnection);
DependencyGraph = new DependencyGraphClient(apiConnection);
} }
/// <summary> /// <summary>
@@ -396,12 +397,17 @@ namespace Octokit
public IActionsClient Actions { get; private set; } public IActionsClient Actions { get; private set; }
public ICodespacesClient Codespaces { get; private set; } public ICodespacesClient Codespaces { get; private set; }
/// <summary> /// <summary>
/// Access GitHub's Copilot for Business API /// Access GitHub's Copilot for Business API
/// </summary> /// </summary>
public ICopilotClient Copilot { get; private set; } public ICopilotClient Copilot { get; private set; }
/// <summary>
/// Access GitHub's Dependency Graph API
/// </summary>
public IDependencyGraphClient DependencyGraph { get; private set; }
static Uri FixUpBaseUri(Uri uri) static Uri FixUpBaseUri(Uri uri)
{ {
Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(uri, nameof(uri));

View File

@@ -922,7 +922,7 @@ namespace Octokit
{ {
return "orgs/{0}/memberships/{1}".FormatUri(org, name); return "orgs/{0}/memberships/{1}".FormatUri(org, name);
} }
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> for the organization's invitations /// Returns the <see cref="Uri"/> for the organization's invitations
/// </summary> /// </summary>
@@ -2568,6 +2568,30 @@ namespace Octokit
return "repos/{0}/{1}/vulnerability-alerts".FormatUri(owner, name); return "repos/{0}/{1}/vulnerability-alerts".FormatUri(owner, name);
} }
/// <summary>
/// Returns the <see cref="Uri"/> for getting Dependency-Diffs between two revisions.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <returns>The <see cref="System.Uri"/> for getting Dependency-Diffs between two revisions for the given repository.</returns>
public static Uri DependencyReview(string owner, string name, string @base, string head)
{
return "repos/{0}/{1}/dependency-graph/compare/{2}...{3}".FormatUri(owner, name, @base, head);
}
/// <summary>
/// Returns the <see cref="Uri"/> for submitting a Dependency Snapshot for the given repository.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns>The <see cref="System.Uri"/> for submitting a Dependency Snapshot for the given repository.</returns>
public static Uri DependencySubmission(string owner, string name)
{
return "repos/{0}/{1}/dependency-graph/snapshots".FormatUri(owner, name);
}
/// <summary> /// <summary>
/// Returns the <see cref="System.Uri"/> for the Deployments API for the given repository. /// Returns the <see cref="System.Uri"/> for the Deployments API for the given repository.
/// </summary> /// </summary>
@@ -3244,6 +3268,28 @@ namespace Octokit
return "repositories/{0}/git/tags".FormatUri(repositoryId); return "repositories/{0}/git/tags".FormatUri(repositoryId);
} }
/// <summary>
/// Returns the <see cref="Uri"/> for getting Dependency-Diffs between two revisions.
/// </summary>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="base">The base revision</param>
/// <param name="head">The head revision</param>
/// <returns>The <see cref="System.Uri"/> for getting Dependency-Diffs between two revisions for the given repository.</returns>
public static Uri DependencyReview(long repositoryId, string @base, string head)
{
return "repositories/{0}/dependency-graph/compare/{1}...{2}".FormatUri(repositoryId, @base, head);
}
/// <summary>
/// Returns the <see cref="Uri"/> for submitting a Dependency Snapshot for the given repository.
/// </summary>
/// <param name="repositoryId">The Id of the repository</param>
/// <returns>The <see cref="System.Uri"/> for submitting a Dependency Snapshot for the given repository.</returns>
public static Uri DependencySubmission(long repositoryId)
{
return "repositories/{0}/dependency-graph/snapshots".FormatUri(repositoryId);
}
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> for the Deployments API for the given repository. /// Returns the <see cref="Uri"/> for the Deployments API for the given repository.
/// </summary> /// </summary>
@@ -5502,7 +5548,7 @@ namespace Octokit
{ {
return "orgs/{0}/actions/runner-groups/{1}/repositories".FormatUri(org, runnerGroupId); return "orgs/{0}/actions/runner-groups/{1}/repositories".FormatUri(org, runnerGroupId);
} }
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> that handles adding or removing of copilot licenses for an organisation /// Returns the <see cref="Uri"/> that handles adding or removing of copilot licenses for an organisation
/// </summary> /// </summary>
@@ -5512,7 +5558,7 @@ namespace Octokit
{ {
return $"orgs/{org}/copilot/billing/selected_users".FormatUri(org); return $"orgs/{org}/copilot/billing/selected_users".FormatUri(org);
} }
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> that handles reading copilot billing settings for an organization /// Returns the <see cref="Uri"/> that handles reading copilot billing settings for an organization
/// </summary> /// </summary>
@@ -5522,7 +5568,7 @@ namespace Octokit
{ {
return $"orgs/{org}/copilot/billing".FormatUri(org); return $"orgs/{org}/copilot/billing".FormatUri(org);
} }
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> that allows for searching across all licenses for an organisation /// Returns the <see cref="Uri"/> that allows for searching across all licenses for an organisation
/// </summary> /// </summary>
@@ -5532,7 +5578,7 @@ namespace Octokit
{ {
return $"orgs/{org}/copilot/billing/seats".FormatUri(org); return $"orgs/{org}/copilot/billing/seats".FormatUri(org);
} }
public static Uri Codespaces() public static Uri Codespaces()
{ {
return _currentUserAllCodespaces; return _currentUserAllCodespaces;

View File

@@ -217,10 +217,15 @@ namespace Octokit
IEmojisClient Emojis { get; } IEmojisClient Emojis { get; }
ICodespacesClient Codespaces { get; } ICodespacesClient Codespaces { get; }
/// <summary> /// <summary>
/// Access to the GitHub Copilot for Business API /// Access to the GitHub Copilot for Business API
/// </summary> /// </summary>
ICopilotClient Copilot { get; } ICopilotClient Copilot { get; }
/// <summary>
/// Access GitHub's Dependency Graph API
/// </summary>
IDependencyGraphClient DependencyGraph { get; }
} }
} }

View File

@@ -0,0 +1,86 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
/// <summary>
/// Describes a new dependency snapshot to create via the <see cref="IDependencySubmissionClient.Create(string,string,NewDependencySnapshot)" /> method.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class NewDependencySnapshot
{
/// <summary>
/// Creates a new Dependency Snapshot.
/// </summary>
/// <param name="version">Required. The version of the repository snapshot submission.</param>
/// <param name="sha">Required. The commit SHA associated with this dependency snapshot. Maximum length: 40 characters.</param>
/// <param name="ref">Required. The repository branch that triggered this snapshot.</param>
/// <param name="scanned">Required. The time at which the snapshot was scanned.</param>
/// <param name="job">Required. The job associated with this dependency snapshot.</param>
/// <param name="detector">Required. A description of the detector used.</param>
public NewDependencySnapshot(long version, string sha, string @ref, string scanned, NewDependencySnapshotJob job, NewDependencySnapshotDetector detector)
{
Ensure.ArgumentNotNullOrEmptyString(sha, nameof(sha));
Ensure.ArgumentNotNullOrEmptyString(@ref, nameof(@ref));
Ensure.ArgumentNotNullOrEmptyString(scanned, nameof(scanned));
Ensure.ArgumentNotNull(job, nameof(job));
Ensure.ArgumentNotNull(detector, nameof(detector));
Version = version;
Sha = sha;
Ref = @ref;
Scanned = scanned;
Job = job;
Detector = detector;
}
/// <summary>
/// Required. The version of the repository snapshot submission.
/// </summary>
public long Version { get; private set; }
/// <summary>
/// Required. The job associated with this dependency snapshot.
/// </summary>
public NewDependencySnapshotJob Job { get; private set; }
/// <summary>
/// Required. The commit SHA associated with this dependency snapshot. Maximum length: 40 characters.
/// </summary>
public string Sha { get; private set; }
/// <summary>
/// Required. The repository branch that triggered this snapshot.
/// </summary>
public string Ref { get; private set; }
/// <summary>
/// Required. A description of the detector used.
/// </summary>
public NewDependencySnapshotDetector Detector { get; private set; }
/// <summary>
/// Optional. User-defined metadata to store domain-specific information limited to 8 keys with scalar values.
/// </summary>
public IDictionary<string, object> Metadata { get; set; }
/// <summary>
/// Optional. A collection of package manifests.
/// </summary>
public IDictionary<string, NewDependencySnapshotManifest> Manifests { get; set; }
/// <summary>
/// Required. The time at which the snapshot was scanned.
/// </summary>
public string Scanned { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Sha: {0}, Version: {1}", Sha, Version);
}
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class NewDependencySnapshotDetector
{
/// <summary>
/// Creates a new Dependency Snapshot Detector.
/// </summary>
/// <param name="name">Required. The name of the detector used.</param>
/// <param name="version">Required. The version of the detector used.</param>
/// <param name="url">Required. The url of the detector used.</param>
public NewDependencySnapshotDetector(string name, string version, string url)
{
Name = name;
Version = version;
Url = url;
}
/// <summary>
/// Required. The name of the detector used.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Required. The version of the detector used.
/// </summary>
public string Version { get; private set; }
/// <summary>
/// Required. The url of the detector used.
/// </summary>
public string Url { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Name: {0}, Version: {1}", Name, Version);
}
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class NewDependencySnapshotJob
{
/// <summary>
/// Creates a new Dependency Snapshot Job.
/// </summary>
/// <param name="id">Required. The external ID of the job.</param>
/// <param name="correlator">Required. Correlator that is used to group snapshots submitted over time.</param>
public NewDependencySnapshotJob(string id, string correlator)
{
Ensure.ArgumentNotNullOrEmptyString(correlator, nameof(correlator));
Id = id;
Correlator = correlator;
}
/// <summary>
/// Required. The external ID of the job.
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Required. Correlator that is used to group snapshots submitted over time.
/// </summary>
public string Correlator { get; private set; }
/// <summary>
/// Optional. The URL for the job.
/// </summary>
public string HtmlUrl { get; set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Id: {0}, HtmlUrl: {1}", Id, HtmlUrl);
}
}
}
}

View File

@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class NewDependencySnapshotManifest
{
/// <summary>
/// Creates a new Dependency Manifest Item.
/// </summary>
/// <param name="name">Required. The name of the manifest.</param>
public NewDependencySnapshotManifest(string name)
{
Ensure.ArgumentNotNullOrEmptyString(name, nameof(name));
Name = name;
}
/// <summary>
/// Required. The name of the manifest.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Optional. The manifest file.
/// </summary>
public NewDependencySnapshotManifestFile File { get; set; }
/// <summary>
/// Optional. User-defined metadata to store domain-specific information limited to 8 keys with scalar values.
/// </summary>
public IDictionary<string, object> Metadata { get; set; }
/// <summary>
/// Optional. A collection of resolved package dependencies.
/// </summary>
public IDictionary<string, NewDependencySnapshotResolvedDependency> Resolved { get; set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Name: {0}", Name);
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class NewDependencySnapshotManifestFile
{
/// <summary>
/// Optional. The path of the manifest file relative to the root of the Git repository.
/// </summary>
public string SourceLocation { get; set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Source Location: {0}", SourceLocation);
}
}
}
}

View File

@@ -0,0 +1,75 @@
using Octokit.Internal;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class NewDependencySnapshotResolvedDependency
{
/// <summary>
/// Optional. Package-url (PURL) of the dependency.
/// </summary>
public string PackageUrl { get; set; }
/// <summary>
/// Optional. User-defined metadata to store domain-specific information limited to 8 keys with scalar values..
/// </summary>
public IDictionary<string, object> Metadata { get; set; }
/// <summary>
/// Optional. A notation of whether a dependency is requested directly by this manifest or is a dependency of another dependency.
/// </summary>
public ResolvedPackageKeyRelationship Relationship { get; set; }
/// <summary>
/// Optional. A notation of whether the dependency is required for the primary build artifact (runtime) or is only used for development.
/// </summary>
public ResolvedPackageKeyScope Scope { get; set; }
/// <summary>
/// Optional. Array of package-url (PURLs) of direct child dependencies.
/// </summary>
public Collection<string> Dependencies { get; set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Package Url: {0}", PackageUrl);
}
}
}
public enum ResolvedPackageKeyRelationship
{
/// <summary>
/// The dependency is requested directly by the manifest.
/// </summary>
[Parameter(Value = "direct")]
Direct,
/// <summary>
/// The dependency is a dependency of another dependency.
/// </summary>
[Parameter(Value = "indirect")]
Indirect,
}
public enum ResolvedPackageKeyScope
{
/// <summary>
/// The dependency is required for the primary build artifact.
/// </summary>
[Parameter(Value = "runtime")]
Runtime,
/// <summary>
/// The dependency is only used for development.
/// </summary>
[Parameter(Value = "development")]
Development,
}
}

View File

@@ -0,0 +1,13 @@
using Octokit.Internal;
namespace Octokit
{
public enum ChangeType
{
[Parameter(Value = "Added")]
Added,
[Parameter(Value = "Removed")]
Removed
}
}

View File

@@ -0,0 +1,127 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class DependencyDiff
{
public DependencyDiff() { }
public DependencyDiff(ChangeType changeType, string manifest, string ecosystem, string name, string version, string packageUrl, string license, string sourceRepositoryUrl, IReadOnlyList<DependencyVulnerability> vulnerabilities, Scope scope)
{
ChangeType = changeType;
Manifest = manifest;
Ecosystem = ecosystem;
Name = name;
Version = version;
PackageUrl = packageUrl;
License = license;
SourceRepositoryUrl = sourceRepositoryUrl;
Vulnerabilities = vulnerabilities;
Scope = scope;
}
/// <summary>
/// The type of the change.
/// </summary>
public StringEnum<ChangeType> ChangeType { get; private set; }
/// <summary>
/// The package manifest of the dependency.
/// </summary>
public string Manifest { get; private set; }
/// <summary>
/// The package ecosystem of the dependency.
/// </summary>
public string Ecosystem { get; private set; }
/// <summary>
/// The package name of the dependency.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The package version of the dependency.
/// </summary>
public string Version { get; private set; }
/// <summary>
/// The package URL of the dependency.
/// </summary>
public string PackageUrl { get; private set; }
/// <summary>
/// The license of the dependency.
/// </summary>
public string License { get; private set; }
/// <summary>
/// The URL of the source repository of the dependency.
/// </summary>
public string SourceRepositoryUrl { get; private set; }
/// <summary>
/// A list of vulnerabilities for the dependency.
/// </summary>
public IReadOnlyList<DependencyVulnerability> Vulnerabilities { get; private set; }
/// <summary>
/// The scope of the dependency.
/// </summary>
public StringEnum<Scope> Scope { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Name: {0}, Version: {1}", Name, Version);
}
}
}
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class DependencyVulnerability
{
public DependencyVulnerability() { }
public DependencyVulnerability(string severity, string advisoryGhsaId, string advisorySummary, string advisoryUrl)
{
Severity = severity;
AdvisoryGhsaId = advisoryGhsaId;
AdvisorySummary = advisorySummary;
AdvisoryUrl = advisoryUrl;
}
/// <summary>
/// The severity of the vulnerability.
/// </summary>
public string Severity { get; private set; }
/// <summary>
/// The GHSA Id of the advisory.
/// </summary>
public string AdvisoryGhsaId { get; private set; }
/// <summary>
/// "A summary of the advisory."
/// </summary>
public string AdvisorySummary { get; private set; }
/// <summary>
/// The URL of the advisory.
/// </summary>
public string AdvisoryUrl { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Advisory URL: {0}", AdvisoryUrl);
}
}
}
}

View File

@@ -0,0 +1,70 @@
using Octokit.Internal;
using System;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class DependencySnapshotSubmission
{
public DependencySnapshotSubmission() { }
public DependencySnapshotSubmission(long id, DateTimeOffset createdAt, DependencySnapshotSubmissionResult result, string message)
{
Id = id;
CreatedAt = createdAt;
Result = result;
Message = message;
}
/// <summary>
/// ID of the created snapshot.
/// </summary>
public long Id { get; private set; }
/// <summary>
/// The time at which the snapshot was created.
/// </summary>
public DateTimeOffset CreatedAt { get; private set; }
/// <summary>
/// The outcome of the dependency snapshot creation.
/// </summary>
public StringEnum<DependencySnapshotSubmissionResult> Result { get; private set; }
/// <summary>
/// A message providing further details about the result, such as why the dependencies were not updated.
/// </summary>
public string Message { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Id: {0} Created at: {1}", Id, CreatedAt);
}
}
}
public enum DependencySnapshotSubmissionResult
{
/// <summary>
/// The snapshot was successfully created and the repository's dependencies were updated.
/// </summary>
[Parameter(Value = "Success")]
Success,
/// <summary>
/// The snapshot was successfully created, but the repository's dependencies were not updated.
/// </summary>
[Parameter(Value = "Accepted")]
Accepted,
/// <summary>
/// The snapshot was malformed.
/// </summary>
[Parameter(Value = "Invalid")]
Invalid
}
}

View File

@@ -0,0 +1,25 @@
using Octokit.Internal;
namespace Octokit
{
public enum Scope
{
/// <summary>
/// The scope of the dependency is not specified or cannot be determined.
/// </summary>
[Parameter(Value = "Unknown")]
Unknown,
/// <summary>
/// Dependency is utilized at runtime and in the development environment.
/// </summary>
[Parameter(Value = "Runtime")]
Runtime,
/// <summary>
/// Dependency is only utilized in the development environment.
/// </summary>
[Parameter(Value = "Development")]
Development,
}
}