diff --git a/.gitignore b/.gitignore index 4184f93872..db81db57cf 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,7 @@ tools/* coverage-results/* # Rider -**/.idea/* \ No newline at end of file +**/.idea/* + +# macOS +.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 0c1ea17bae..77504b0729 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,11 @@ "files.insertFinalNewline": true, "editor.detectIndentation": false, "editor.tabSize": 2, - "editor.insertSpaces": true + "editor.insertSpaces": true, + "[csharp]": { + "editor.tabSize": 4 + }, + "explorer.fileNesting.patterns": { + "*.cs": "I${capture}.cs", + }, } diff --git a/Octokit.AsyncPaginationExtension/Extensions.cs b/Octokit.AsyncPaginationExtension/Extensions.cs index f41c07fbfe..7e2f6221a5 100644 --- a/Octokit.AsyncPaginationExtension/Extensions.cs +++ b/Octokit.AsyncPaginationExtension/Extensions.cs @@ -235,14 +235,6 @@ public static IPaginatedList GetAllAsync(this IRepoCollaboratorsCl public static IPaginatedList GetAllAsync(this IRepoCollaboratorsClient t, long repositoryId, RepositoryCollaboratorListRequest request, int pageSize = DEFAULT_PAGE_SIZE) => pageSize > 0 ? new PaginatedList(options => t.GetAll(repositoryId, request, options), pageSize) : throw new ArgumentOutOfRangeException(nameof(pageSize), pageSize, "The page size must be positive."); - /// - public static IPaginatedList ListAllRunnerGroupOrganizationsForEnterpriseAsync(this IActionsSelfHostedRunnerGroupsClient t, string enterprise, long runnerGroupId, int pageSize = DEFAULT_PAGE_SIZE) - => pageSize > 0 ? new PaginatedList(options => t.ListAllRunnerGroupOrganizationsForEnterprise(enterprise, runnerGroupId, options), pageSize) : throw new ArgumentOutOfRangeException(nameof(pageSize), pageSize, "The page size must be positive."); - - /// - public static IPaginatedList ListAllRunnerGroupRepositoriesForOrganizationAsync(this IActionsSelfHostedRunnerGroupsClient t, string org, long runnerGroupId, int pageSize = DEFAULT_PAGE_SIZE) - => pageSize > 0 ? new PaginatedList(options => t.ListAllRunnerGroupRepositoriesForOrganization(org, runnerGroupId, options), pageSize) : throw new ArgumentOutOfRangeException(nameof(pageSize), pageSize, "The page size must be positive."); - /// public static IPaginatedList GetAllAsync(this IProjectColumnsClient t, int projectId, int pageSize = DEFAULT_PAGE_SIZE) => pageSize > 0 ? new PaginatedList(options => t.GetAll(projectId, options), pageSize) : throw new ArgumentOutOfRangeException(nameof(pageSize), pageSize, "The page size must be positive."); diff --git a/Octokit.Reactive/Clients/IObservableActionsSelfHostedRunnerGroupsClient.cs b/Octokit.Reactive/Clients/IObservableActionsSelfHostedRunnerGroupsClient.cs index 4e6aaa2987..04c6e90d9b 100644 --- a/Octokit.Reactive/Clients/IObservableActionsSelfHostedRunnerGroupsClient.cs +++ b/Octokit.Reactive/Clients/IObservableActionsSelfHostedRunnerGroupsClient.cs @@ -117,7 +117,7 @@ public interface IObservableActionsSelfHostedRunnerGroupsClient /// /// The enterprise name /// The runner group id - IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId); + IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId); /// /// List organization access to a self-hosted runner group in an enterprise @@ -128,7 +128,7 @@ public interface IObservableActionsSelfHostedRunnerGroupsClient /// The enterprise name /// The runner group id /// Options for changing the API response - IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options); + IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options); /// /// List repository access to a self-hosted runner group in an organization @@ -138,7 +138,7 @@ public interface IObservableActionsSelfHostedRunnerGroupsClient /// /// The organization name /// The runner group id - IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId); + IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId); /// /// List repository access to a self-hosted runner group in an organization @@ -149,7 +149,7 @@ public interface IObservableActionsSelfHostedRunnerGroupsClient /// The organization name /// The runner group id /// Options for changing the API response - IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options); + IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options); } } diff --git a/Octokit.Reactive/Clients/IObservableActionsWorkflowsClient.cs b/Octokit.Reactive/Clients/IObservableActionsWorkflowsClient.cs index 9c5f055bbc..74773d0478 100644 --- a/Octokit.Reactive/Clients/IObservableActionsWorkflowsClient.cs +++ b/Octokit.Reactive/Clients/IObservableActionsWorkflowsClient.cs @@ -12,7 +12,7 @@ namespace Octokit.Reactive public interface IObservableActionsWorkflowsClient { /// - /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// Manually triggers a GitHub Actions workflow run in a repository by slug. /// /// /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event @@ -24,7 +24,7 @@ public interface IObservableActionsWorkflowsClient IObservable CreateDispatch(string owner, string name, string workflowFileName, CreateWorkflowDispatch createDispatch); /// - /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// Manually triggers a GitHub Actions workflow run in a repository by slug. /// /// /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event @@ -35,6 +35,27 @@ public interface IObservableActionsWorkflowsClient /// The parameters to use to trigger the workflow run. IObservable CreateDispatch(string owner, string name, long workflowId, CreateWorkflowDispatch createDispatch); + /// + /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The workflow file name. + /// The parameters to use to trigger the workflow run. + IObservable CreateDispatch(long repositoryId, string workflowFileName, CreateWorkflowDispatch createDispatch); + + /// + /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The Id of the workflow. + /// The parameters to use to trigger the workflow run. + IObservable CreateDispatch(long repositoryId, long workflowId, CreateWorkflowDispatch createDispatch); /// /// Disables a specific workflow in a repository by Id. /// diff --git a/Octokit.Reactive/Clients/IObservableMetaClient.cs b/Octokit.Reactive/Clients/IObservableMetaClient.cs index 97d63d19e3..3555670358 100644 --- a/Octokit.Reactive/Clients/IObservableMetaClient.cs +++ b/Octokit.Reactive/Clients/IObservableMetaClient.cs @@ -10,6 +10,11 @@ namespace Octokit.Reactive /// public interface IObservableMetaClient { + /// + /// Returns a client to get public keys for validating request signatures. + /// + IObservablePublicKeysClient PublicKeys { get; } + /// /// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation. /// diff --git a/Octokit.Reactive/Clients/IObservableOauthClient.cs b/Octokit.Reactive/Clients/IObservableOauthClient.cs index 5313caa819..895c9006de 100644 --- a/Octokit.Reactive/Clients/IObservableOauthClient.cs +++ b/Octokit.Reactive/Clients/IObservableOauthClient.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace Octokit.Reactive { @@ -22,8 +23,9 @@ public interface IObservableOauthClient /// an access token using this method. /// /// + /// /// - IObservable CreateAccessToken(OauthTokenRequest request); + IObservable CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default); /// /// Makes a request to initiate the device flow authentication. @@ -33,25 +35,28 @@ public interface IObservableOauthClient /// This request also returns a device verification code that you must use to receive an access token to check the status of user authentication. /// /// + /// /// - IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request); + IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default); /// - /// Makes a request to get an access token using the response from . + /// Makes a request to get an access token using the response from . /// /// /// Will poll the access token endpoint, until the device and user codes expire or the user has successfully authorized the app with a valid user code. /// /// The client Id you received from GitHub when you registered the application. - /// The response you received from + /// The response you received from + /// /// - IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse); + IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default); /// - /// Makes a request to get an access token using the refresh token returned in . + /// Makes a request to get an access token using the refresh token returned in . /// /// Token renewal request. + /// /// with the new token set. - IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request); + IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default); } } diff --git a/Octokit.Reactive/Clients/IObservablePublicKeysClient.cs b/Octokit.Reactive/Clients/IObservablePublicKeysClient.cs new file mode 100644 index 0000000000..02aae89a0a --- /dev/null +++ b/Octokit.Reactive/Clients/IObservablePublicKeysClient.cs @@ -0,0 +1,20 @@ +using System; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's meta public keys API. + /// + /// + /// See the Secret scanning documentation for more details. + /// + public interface IObservablePublicKeysClient + { + /// + /// Retrieves public keys for validating request signatures. + /// + /// Thrown when a general API error occurs. + /// An containing public keys for validating request signatures. + IObservable Get(PublicKeyType keysType); + } +} diff --git a/Octokit.Reactive/Clients/ObservableActionsSelfHostedRunnerGroupsClient.cs b/Octokit.Reactive/Clients/ObservableActionsSelfHostedRunnerGroupsClient.cs index 7f7c0ec5a5..bb4369125e 100644 --- a/Octokit.Reactive/Clients/ObservableActionsSelfHostedRunnerGroupsClient.cs +++ b/Octokit.Reactive/Clients/ObservableActionsSelfHostedRunnerGroupsClient.cs @@ -175,7 +175,7 @@ public IObservable ListAllRunnersForOrganizationRunnerGroup(stri /// /// The enterprise name /// The runner group ID - public IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId) + public IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId) { return ListAllRunnerGroupOrganizationsForEnterprise(enterprise, runnerGroupId, ApiOptions.None); } @@ -189,12 +189,12 @@ public IObservable ListAllRunnerGroupOrganizationsForEnterprise(st /// The enterprise name /// The runner group ID /// Options for changing the API response - public IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options) + public IObservable ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options) { Ensure.ArgumentNotNullOrEmptyString(enterprise, nameof(enterprise)); Ensure.ArgumentNotNull(options, nameof(options)); - return _connection.GetAndFlattenAllPages(ApiUrls.ActionsListEnterpriseRunnerGroupOrganizations(enterprise, runnerGroupId), options); + return _client.ListAllRunnerGroupOrganizationsForEnterprise(enterprise, runnerGroupId, options).ToObservable(); } /// @@ -205,7 +205,7 @@ public IObservable ListAllRunnerGroupOrganizationsForEnterprise(st /// /// The organization name /// The runner group ID - public IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId) + public IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId) { return ListAllRunnerGroupRepositoriesForOrganization(org, runnerGroupId, ApiOptions.None); } @@ -219,12 +219,12 @@ public IObservable ListAllRunnerGroupRepositoriesForOrganization(str /// The organization name /// The runner group ID /// Options for changing the API response - public IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options) + public IObservable ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options) { Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); Ensure.ArgumentNotNull(options, nameof(options)); - return _connection.GetAndFlattenAllPages(ApiUrls.ActionsListOrganizationRunnerGroupRepositories(org, runnerGroupId), options); + return _client.ListAllRunnerGroupRepositoriesForOrganization(org, runnerGroupId, options).ToObservable(); } } diff --git a/Octokit.Reactive/Clients/ObservableActionsWorkflowsClient.cs b/Octokit.Reactive/Clients/ObservableActionsWorkflowsClient.cs index bd6bb02224..7ec222b151 100644 --- a/Octokit.Reactive/Clients/ObservableActionsWorkflowsClient.cs +++ b/Octokit.Reactive/Clients/ObservableActionsWorkflowsClient.cs @@ -23,7 +23,7 @@ public ObservableActionsWorkflowsClient(IGitHubClient client) } /// - /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// Manually triggers a GitHub Actions workflow run in a repository by slug. /// /// /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event @@ -43,7 +43,7 @@ public IObservable CreateDispatch(string owner, string name, string workfl } /// - /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// Manually triggers a GitHub Actions workflow run in a repository by slug. /// /// /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event @@ -61,6 +61,39 @@ public IObservable CreateDispatch(string owner, string name, long workflow return _client.CreateDispatch(owner, name, workflowId, createDispatch).ToObservable(); } + /// + /// Manually triggers a GitHub Actions workflow run in a repository by slug. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The workflow file name. + /// The parameters to use to trigger the workflow run. + public IObservable CreateDispatch(long repositoryId, string workflowFileName, CreateWorkflowDispatch createDispatch) + { + Ensure.ArgumentNotNullOrEmptyString(workflowFileName, nameof(workflowFileName)); + Ensure.ArgumentNotNull(createDispatch, nameof(createDispatch)); + + return _client.CreateDispatch(repositoryId, workflowFileName, createDispatch).ToObservable(); + } + + /// + /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The Id of the workflow. + /// The parameters to use to trigger the workflow run. + public IObservable CreateDispatch(long repositoryId, long workflowId, CreateWorkflowDispatch createDispatch) + { + Ensure.ArgumentNotNull(createDispatch, nameof(createDispatch)); + + return _client.CreateDispatch(repositoryId, workflowId, createDispatch).ToObservable(); + } + /// /// Disables a specific workflow in a repository by Id. /// diff --git a/Octokit.Reactive/Clients/ObservableMetaClient.cs b/Octokit.Reactive/Clients/ObservableMetaClient.cs index 3df88872d7..d8283ed623 100644 --- a/Octokit.Reactive/Clients/ObservableMetaClient.cs +++ b/Octokit.Reactive/Clients/ObservableMetaClient.cs @@ -18,9 +18,16 @@ public ObservableMetaClient(IGitHubClient client) { Ensure.ArgumentNotNull(client, nameof(client)); + PublicKeys = new ObservablePublicKeysClient(client); + _client = client.Meta; } + /// + /// Returns a client to manage get public keys for validating request signatures. + /// + public IObservablePublicKeysClient PublicKeys { get; private set; } + /// /// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation. /// diff --git a/Octokit.Reactive/Clients/ObservableOauthClient.cs b/Octokit.Reactive/Clients/ObservableOauthClient.cs index 1e4f42ef66..4d58da79fe 100644 --- a/Octokit.Reactive/Clients/ObservableOauthClient.cs +++ b/Octokit.Reactive/Clients/ObservableOauthClient.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Threading.Tasks; +using System.Threading; namespace Octokit.Reactive { @@ -23,24 +24,24 @@ public Uri GetGitHubLoginUrl(OauthLoginRequest request) return _client.Oauth.GetGitHubLoginUrl(request); } - public IObservable CreateAccessToken(OauthTokenRequest request) + public IObservable CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default) { - return _client.Oauth.CreateAccessToken(request).ToObservable(); + return _client.Oauth.CreateAccessToken(request, cancellationToken).ToObservable(); } - public IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request) + public IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default) { - return _client.Oauth.InitiateDeviceFlow(request).ToObservable(); + return _client.Oauth.InitiateDeviceFlow(request, cancellationToken).ToObservable(); } - public IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse) + public IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default) { - return _client.Oauth.CreateAccessTokenForDeviceFlow(clientId, deviceFlowResponse).ToObservable(); + return _client.Oauth.CreateAccessTokenForDeviceFlow(clientId, deviceFlowResponse, cancellationToken).ToObservable(); } - public IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request) + public IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default) { - return _client.Oauth.CreateAccessTokenFromRenewalToken(request) + return _client.Oauth.CreateAccessTokenFromRenewalToken(request, cancellationToken) .ToObservable(); } } diff --git a/Octokit.Reactive/Clients/ObservablePublicKeysClient.cs b/Octokit.Reactive/Clients/ObservablePublicKeysClient.cs new file mode 100644 index 0000000000..5339645561 --- /dev/null +++ b/Octokit.Reactive/Clients/ObservablePublicKeysClient.cs @@ -0,0 +1,34 @@ +using System; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's public keys API. + /// + /// + /// See the Secret scanning documentation for more details. + /// + public class ObservablePublicKeysClient : IObservablePublicKeysClient + { + private readonly IPublicKeysClient _client; + + public ObservablePublicKeysClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, nameof(client)); + + _client = client.Meta.PublicKeys; + } + + /// + /// Retrieves public keys for validating request signatures. + /// + /// Thrown when a general API error occurs. + /// An containing public keys for validating request signatures. + public IObservable Get(PublicKeyType keysType) + { + return _client.Get(keysType).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/IObservableCodespacesClient.cs b/Octokit.Reactive/IObservableCodespacesClient.cs index cb3e67ba25..07e40ef8e3 100644 --- a/Octokit.Reactive/IObservableCodespacesClient.cs +++ b/Octokit.Reactive/IObservableCodespacesClient.cs @@ -16,5 +16,7 @@ public interface IObservableCodespacesClient IObservable Get(string codespaceName); IObservable Start(string codespaceName); IObservable Stop(string codespaceName); + IObservable GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null); + IObservable Create(string owner, string repo, NewCodespace newCodespace); } } \ No newline at end of file diff --git a/Octokit.Reactive/ObservableCodespacesClient.cs b/Octokit.Reactive/ObservableCodespacesClient.cs index b8c6575346..477e65dc59 100644 --- a/Octokit.Reactive/ObservableCodespacesClient.cs +++ b/Octokit.Reactive/ObservableCodespacesClient.cs @@ -25,6 +25,13 @@ public IObservable GetAll() return _client.GetAll().ToObservable(); } + public IObservable GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null) + { + Ensure.ArgumentNotNull(repoOwner, nameof(repoOwner)); + Ensure.ArgumentNotNull(repoName, nameof(repoName)); + return _client.GetAvailableMachinesForRepo(repoOwner, repoName, reference).ToObservable(); + } + public IObservable GetForRepository(string owner, string repo) { Ensure.ArgumentNotNull(owner, nameof(owner)); @@ -43,5 +50,13 @@ public IObservable Stop(string codespaceName) Ensure.ArgumentNotNull(codespaceName, nameof(codespaceName)); return _client.Stop(codespaceName).ToObservable(); } + + public IObservable Create(string owner, string repo, NewCodespace newCodespace) + { + Ensure.ArgumentNotNull(owner, nameof(owner)); + Ensure.ArgumentNotNull(repo, nameof(repo)); + Ensure.ArgumentNotNull(newCodespace, nameof(newCodespace)); + return _client.Create(owner, repo, newCodespace).ToObservable(); + } } } \ No newline at end of file diff --git a/Octokit.Tests.Conventions/Octokit.Tests.Conventions.csproj b/Octokit.Tests.Conventions/Octokit.Tests.Conventions.csproj index 57bcf77718..94cbc9dc43 100644 --- a/Octokit.Tests.Conventions/Octokit.Tests.Conventions.csproj +++ b/Octokit.Tests.Conventions/Octokit.Tests.Conventions.csproj @@ -33,9 +33,9 @@ - - - + + + diff --git a/Octokit.Tests.Integration/Clients/ActionsWorkflowsClientTests.cs b/Octokit.Tests.Integration/Clients/ActionsWorkflowsClientTests.cs index 59f9e923a5..2ea206b408 100644 --- a/Octokit.Tests.Integration/Clients/ActionsWorkflowsClientTests.cs +++ b/Octokit.Tests.Integration/Clients/ActionsWorkflowsClientTests.cs @@ -105,14 +105,17 @@ public async Task CanDispatchWorkflow() { var owner = context.Repository.Owner.Login; var name = context.Repository.Name; + var repoId = context.Repository.Id; var workflowFileName = await CreateWorkflow(github, context); var reference = "main"; await fixture.CreateDispatch(owner, name, workflowFileName, new CreateWorkflowDispatch(reference)); + await fixture.CreateDispatch(repoId, workflowFileName, new CreateWorkflowDispatch(reference)); var workflowId = await GetWorkflowId(github, context, workflowFileName); await fixture.CreateDispatch(owner, name, workflowId, new CreateWorkflowDispatch(reference)); + await fixture.CreateDispatch(repoId, workflowId, new CreateWorkflowDispatch(reference)); } } diff --git a/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs b/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs index f1b6342384..01047a060f 100644 --- a/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs @@ -56,4 +56,19 @@ public async Task CanStopCodespace() var retrieved = await _fixture.Stop(codespaceName); Assert.NotNull(retrieved); } + + [IntegrationTest] + public async Task CanGetAvailableMachinesForRepo() + { + var retrieved = await _fixture.GetAvailableMachinesForRepo(Helper.UserName, Helper.RepositoryWithCodespaces); + Assert.NotNull(retrieved); + } + + [IntegrationTest] + public async Task CanCreateCodespace() + { + MachinesCollection machinesCollection = (await _fixture.GetAvailableMachinesForRepo(Helper.UserName, Helper.RepositoryWithCodespaces)); + var retrieved = await _fixture.Create(Helper.UserName, Helper.RepositoryWithCodespaces, new NewCodespace(machinesCollection.Machines.First())); + Assert.NotNull(retrieved); + } } diff --git a/Octokit.Tests.Integration/Clients/PublicKeysClientTest.cs b/Octokit.Tests.Integration/Clients/PublicKeysClientTest.cs new file mode 100644 index 0000000000..a6b2effb1f --- /dev/null +++ b/Octokit.Tests.Integration/Clients/PublicKeysClientTest.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Integration.Clients +{ + public class PublicKeysClientTests + { + public class TheGetMethod + { + [IntegrationTest] + public async Task CanRetrievePublicKeys() + { + var github = Helper.GetAnonymousClient(); + + var result = await github.Meta.PublicKeys.Get(PublicKeyType.SecretScanning); + + Assert.NotNull(result); + Assert.Equal(2, result.PublicKeys.Count); + + Assert.NotNull(result.PublicKeys[0].KeyIdentifier); + Assert.NotNull(result.PublicKeys[0].Key); + + Assert.NotNull(result.PublicKeys[1].KeyIdentifier); + Assert.NotNull(result.PublicKeys[1].Key); + } + } + } +} diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 42463c3ddc..64664094ab 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -34,10 +34,10 @@ - + - - + + diff --git a/Octokit.Tests/Clients/ActionsSelfHostedRunnerGroupsClientTests.cs b/Octokit.Tests/Clients/ActionsSelfHostedRunnerGroupsClientTests.cs index a1886ad8d2..f9af6067ff 100644 --- a/Octokit.Tests/Clients/ActionsSelfHostedRunnerGroupsClientTests.cs +++ b/Octokit.Tests/Clients/ActionsSelfHostedRunnerGroupsClientTests.cs @@ -217,7 +217,7 @@ public async Task RequestsCorrectUrl() await client.ListAllRunnerGroupOrganizationsForEnterprise("fake", 1); - connection.Received().GetAll( + connection.Received().GetAll( Arg.Is(u => u.ToString() == "enterprises/fake/actions/runner-groups/1/organizations"), Args.ApiOptions); } @@ -248,7 +248,7 @@ public async Task RequestsCorrectUrl() await client.ListAllRunnerGroupRepositoriesForOrganization("fake", 1, ApiOptions.None); - connection.Received().GetAll( + connection.Received().GetAll( Arg.Is(u => u.ToString() == "orgs/fake/actions/runner-groups/1/repositories"), Args.ApiOptions); } diff --git a/Octokit.Tests/Clients/ActionsWorkflowsClientTests.cs b/Octokit.Tests/Clients/ActionsWorkflowsClientTests.cs index 45642f27fe..f889b2de8c 100644 --- a/Octokit.Tests/Clients/ActionsWorkflowsClientTests.cs +++ b/Octokit.Tests/Clients/ActionsWorkflowsClientTests.cs @@ -32,7 +32,7 @@ public void AreNotNull() public class TheCreateDispatchMethod { [Fact] - public async Task RequestsCorrectUrlByWorkflowId() + public async Task RequestsCorrectUrlByWorkflowIdRepoSlug() { var connection = Substitute.For(); var client = new ActionsWorkflowsClient(connection); @@ -47,7 +47,7 @@ public async Task RequestsCorrectUrlByWorkflowId() } [Fact] - public async Task RequestsCorrectUrlByWorkflowFileName() + public async Task RequestsCorrectUrlByWorkflowFileNameRepoSlug() { var connection = Substitute.For(); var client = new ActionsWorkflowsClient(connection); @@ -61,6 +61,36 @@ public async Task RequestsCorrectUrlByWorkflowFileName() createDispatch); } + [Fact] + public async Task RequestsCorrectUrlByWorkflowIdRepoId() + { + var connection = Substitute.For(); + var client = new ActionsWorkflowsClient(connection); + + var createDispatch = new CreateWorkflowDispatch("ref"); + + await client.CreateDispatch(321, 123, createDispatch); + + connection.Received().Post( + Arg.Is(u => u.ToString() == "repositories/321/actions/workflows/123/dispatches"), + createDispatch); + } + + [Fact] + public async Task RequestsCorrectUrlByWorkflowFileNameRepoId() + { + var connection = Substitute.For(); + var client = new ActionsWorkflowsClient(connection); + + var createDispatch = new CreateWorkflowDispatch("ref"); + + await client.CreateDispatch(321, "main.yaml", createDispatch); + + connection.Received().Post( + Arg.Is(u => u.ToString() == "repositories/321/actions/workflows/main.yaml/dispatches"), + createDispatch); + } + [Fact] public async Task EnsuresNonNullArguments() { @@ -77,6 +107,9 @@ public async Task EnsuresNonNullArguments() await Assert.ThrowsAsync(() => client.CreateDispatch("fake", null, "main.yaml", createDispatch)); await Assert.ThrowsAsync(() => client.CreateDispatch("fake", "repo", null, createDispatch)); await Assert.ThrowsAsync(() => client.CreateDispatch("fake", "repo", "main.yaml", null)); + + await Assert.ThrowsAsync(() => client.CreateDispatch(4321, 123, null)); + await Assert.ThrowsAsync(() => client.CreateDispatch(4321, null, createDispatch)); } [Fact] @@ -93,6 +126,8 @@ public async Task EnsuresNonEmptyArguments() await Assert.ThrowsAsync(() => client.CreateDispatch("", "repo", "main.yaml", createDispatch)); await Assert.ThrowsAsync(() => client.CreateDispatch("fake", "", "main.yaml", createDispatch)); await Assert.ThrowsAsync(() => client.CreateDispatch("fake", "repo", "", createDispatch)); + + await Assert.ThrowsAsync(() => client.CreateDispatch(4321, "", createDispatch)); } } diff --git a/Octokit.Tests/Clients/CodespacesClientTests.cs b/Octokit.Tests/Clients/CodespacesClientTests.cs index 41fad4a51c..12067b85a7 100644 --- a/Octokit.Tests/Clients/CodespacesClientTests.cs +++ b/Octokit.Tests/Clients/CodespacesClientTests.cs @@ -68,5 +68,24 @@ public void RequestsCorrectStopUrl() client.Stop("codespaceName"); connection.Received().Post(Arg.Is(u => u.ToString() == "user/codespaces/codespaceName/stop")); } + + [Fact] + public void RequestsCorrectGetAvailableMachinesForRepoUrl() + { + var connection = Substitute.For(); + var client = new CodespacesClient(connection); + client.GetAvailableMachinesForRepo("owner", "repo"); + connection.Received().Get(Arg.Is(u => u.ToString() == "repos/owner/repo/codespaces/machines")); + } + + [Fact] + public void RequestsCorrectCreateNewCodespaceUrl() + { + var connection = Substitute.For(); + var client = new CodespacesClient(connection); + NewCodespace newCodespace = new NewCodespace(new Machine()); + client.Create("owner", "repo", newCodespace); + connection.Received().Post(Arg.Is(u => u.ToString() == "repos/owner/repo/codespaces"), newCodespace); + } } } diff --git a/Octokit.Tests/Clients/PublicKeysClientTests.cs b/Octokit.Tests/Clients/PublicKeysClientTests.cs new file mode 100644 index 0000000000..ee416a6604 --- /dev/null +++ b/Octokit.Tests/Clients/PublicKeysClientTests.cs @@ -0,0 +1,90 @@ +using System; +using System.Threading.Tasks; +using NSubstitute; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class PublicKeysClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new PublicKeysClient(null)); + } + } + + public class TheGetMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new PublicKeysClient(connection); + + await client.Get(PublicKeyType.CopilotApi); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "meta/public_keys/copilot_api")); + } + + [Fact] + public async Task RequestsCopilotApiPublicKeysEndpoint() + { + var publicKeys = new MetaPublicKeys(publicKeys: new[] { + new MetaPublicKey("4fe6b016179b74078ade7581abf4e84fb398c6fae4fb973972235b84fcd70ca3", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELPuPiLVQbHY/clvpNnY+0BzYIXgo\nS0+XhEkTWUZEEznIVpS3rQseDTG6//gEWr4j9fY35+dGOxwOx3Z9mK3i7w==\n-----END PUBLIC KEY-----\n", true), + new MetaPublicKey("df3454252d91570ae1bc597182d1183c7a8d42ff0ae96e0f2be4ba278d776546", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl5xbyr5bmETCJzqAvDnYl1ZKJrkf\n89Nyq5j06TTKrnHXXDw4FYNY1uF2S/w6EOaxbf9BxOidCLvjJ8ZgKzNpww==\n-----END PUBLIC KEY-----\n", false) + }); + + var apiConnection = Substitute.For(); + apiConnection.Get(Arg.Is(u => u.ToString() == "meta/public_keys/copilot_api")).Returns(Task.FromResult(publicKeys)); + + var client = new PublicKeysClient(apiConnection); + + var result = await client.Get(PublicKeyType.CopilotApi); + + Assert.Equal(2, result.PublicKeys.Count); + Assert.Equal("4fe6b016179b74078ade7581abf4e84fb398c6fae4fb973972235b84fcd70ca3", result.PublicKeys[0].KeyIdentifier); + Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELPuPiLVQbHY/clvpNnY+0BzYIXgo\nS0+XhEkTWUZEEznIVpS3rQseDTG6//gEWr4j9fY35+dGOxwOx3Z9mK3i7w==\n-----END PUBLIC KEY-----\n", result.PublicKeys[0].Key); + Assert.True(result.PublicKeys[0].IsCurrent); + + Assert.Equal("df3454252d91570ae1bc597182d1183c7a8d42ff0ae96e0f2be4ba278d776546", result.PublicKeys[1].KeyIdentifier); + Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl5xbyr5bmETCJzqAvDnYl1ZKJrkf\n89Nyq5j06TTKrnHXXDw4FYNY1uF2S/w6EOaxbf9BxOidCLvjJ8ZgKzNpww==\n-----END PUBLIC KEY-----\n", result.PublicKeys[1].Key); + Assert.False(result.PublicKeys[1].IsCurrent); + + apiConnection.Received() + .Get(Arg.Is(u => u.ToString() == "meta/public_keys/copilot_api")); + } + + [Fact] + public async Task RequestSecretScanningPublicKeysEndpoint() + { + var publicKeys = new MetaPublicKeys(publicKeys: new[] { + new MetaPublicKey("90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n", false), + new MetaPublicKey("bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n", true) + }); + + var apiConnection = Substitute.For(); + apiConnection.Get(Arg.Is(u => u.ToString() == "meta/public_keys/secret_scanning")).Returns(Task.FromResult(publicKeys)); + + var client = new PublicKeysClient(apiConnection); + + var result = await client.Get(PublicKeyType.SecretScanning); + + Assert.Equal(2, result.PublicKeys.Count); + Assert.Equal("90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a", result.PublicKeys[0].KeyIdentifier); + Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n", result.PublicKeys[0].Key); + Assert.False(result.PublicKeys[0].IsCurrent); + + Assert.Equal("bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c", result.PublicKeys[1].KeyIdentifier); + Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n", result.PublicKeys[1].Key); + Assert.True(result.PublicKeys[1].IsCurrent); + + apiConnection.Received() + .Get(Arg.Is(u => u.ToString() == "meta/public_keys/secret_scanning")); + } + } + } +} diff --git a/Octokit.Tests/Models/MetaPublicKeysTests.cs b/Octokit.Tests/Models/MetaPublicKeysTests.cs new file mode 100644 index 0000000000..9e5f64ea89 --- /dev/null +++ b/Octokit.Tests/Models/MetaPublicKeysTests.cs @@ -0,0 +1,44 @@ +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class MetaPublicKeysTests + { + [Fact] + public void CanBeDeserialized() + { + const string json = @"{ + ""public_keys"": [ + { + ""key_identifier"": ""90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a"", + ""key"": ""-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n"", + ""is_current"": false + }, + { + ""key_identifier"": ""bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c"", + ""key"": ""-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n"", + ""is_current"": true + } + ] +} +"; + var serializer = new SimpleJsonSerializer(); + + var keys = serializer.Deserialize(json); + + Assert.NotNull(keys); + Assert.Equal(2, keys.PublicKeys.Count); + + var key1 = keys.PublicKeys[0]; + Assert.Equal("90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a", key1.KeyIdentifier); + Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n", key1.Key); + Assert.False(key1.IsCurrent); + + var key2 = keys.PublicKeys[1]; + Assert.Equal("bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c", key2.KeyIdentifier); + Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n", key2.Key); + Assert.True(key2.IsCurrent); + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 0f9027ec89..90d44fcbc7 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -29,14 +29,14 @@ - + - + - - + + diff --git a/Octokit.Tests/Reactive/ObservableActionsWorkflowsClientTests.cs b/Octokit.Tests/Reactive/ObservableActionsWorkflowsClientTests.cs index 0bf644a46f..2fb65d9b1a 100644 --- a/Octokit.Tests/Reactive/ObservableActionsWorkflowsClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableActionsWorkflowsClientTests.cs @@ -33,7 +33,7 @@ public void AreNotNull() public class TheCreateDispatchMethod { [Fact] - public async Task RequestsCorrectUrlByWorkflowId() + public async Task RequestsCorrectUrlByWorkflowIdRepoSlug() { var connection = Substitute.For(); var client = new ObservableActionsWorkflowsClient(connection); @@ -46,7 +46,7 @@ public async Task RequestsCorrectUrlByWorkflowId() } [Fact] - public async Task RequestsCorrectUrlByWorkflowFileName() + public async Task RequestsCorrectUrlByWorkflowFileNameRepoSlug() { var connection = Substitute.For(); var client = new ObservableActionsWorkflowsClient(connection); @@ -57,6 +57,31 @@ public async Task RequestsCorrectUrlByWorkflowFileName() connection.Received().Actions.Workflows.CreateDispatch("fake", "repo", "main.yaml", createDispatch); } + [Fact] + public async Task RequestsCorrectUrlByWorkflowIdRepoId() + { + var connection = Substitute.For(); + var client = new ObservableActionsWorkflowsClient(connection); + + var createDispatch = new CreateWorkflowDispatch("ref"); + + client.CreateDispatch(1234, 123, createDispatch); + + connection.Received().Actions.Workflows.CreateDispatch(1234, 123, createDispatch); + } + + [Fact] + public async Task RequestsCorrectUrlByWorkflowFileNameRepoId() + { + var connection = Substitute.For(); + var client = new ObservableActionsWorkflowsClient(connection); + + var createDispatch = new CreateWorkflowDispatch("ref"); + + client.CreateDispatch(1234, "main.yaml", createDispatch); + + connection.Received().Actions.Workflows.CreateDispatch(1234, "main.yaml", createDispatch); + } [Fact] public async Task EnsuresNonNullArguments() @@ -74,6 +99,9 @@ public async Task EnsuresNonNullArguments() Assert.Throws(() => client.CreateDispatch("fake", null, "main.yaml", createDispatch)); Assert.Throws(() => client.CreateDispatch("fake", "repo", null, createDispatch)); Assert.Throws(() => client.CreateDispatch("fake", "repo", "main.yaml", null)); + + Assert.Throws(() => client.CreateDispatch(4321, 123, null)); + Assert.Throws(() => client.CreateDispatch(4321, null, createDispatch)); } [Fact] @@ -90,6 +118,8 @@ public async Task EnsuresNonEmptyArguments() Assert.Throws(() => client.CreateDispatch("", "repo", "main.yaml", createDispatch)); Assert.Throws(() => client.CreateDispatch("fake", "", "main.yaml", createDispatch)); Assert.Throws(() => client.CreateDispatch("fake", "repo", "", createDispatch)); + + Assert.Throws(() => client.CreateDispatch(4321, "", createDispatch)); } } diff --git a/Octokit.Tests/Reactive/ObservablePublicKeysClientTests.cs b/Octokit.Tests/Reactive/ObservablePublicKeysClientTests.cs new file mode 100644 index 0000000000..14939de515 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservablePublicKeysClientTests.cs @@ -0,0 +1,33 @@ +using System; +using NSubstitute; +using Octokit.Reactive; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservablePublicKeysClientTests + { + public class TheGetMethod + { + [Fact] + public void CallsIntoClient() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePublicKeysClient(gitHubClient); + + client.Get(PublicKeyType.SecretScanning); + + gitHubClient.Meta.PublicKeys.Received(1).Get(PublicKeyType.SecretScanning); + } + } + + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new ObservablePublicKeysClient((IGitHubClient)null)); + } + } + } +} diff --git a/Octokit.Tests/SimpleJsonTests.cs b/Octokit.Tests/SimpleJsonTests.cs new file mode 100644 index 0000000000..ffc211cee4 --- /dev/null +++ b/Octokit.Tests/SimpleJsonTests.cs @@ -0,0 +1,36 @@ +using Octokit; +using System.Threading.Tasks; +using Xunit; + +public class SimpleJsonTests +{ + [Theory] + [InlineData("\"abc\"", "abc")] + [InlineData(" \"abc\" ", "abc")] + [InlineData("\" abc \" ", " abc ")] + [InlineData("\"abc\\\"def\"", "abc\"def")] + [InlineData("\"abc\\r\\ndef\"", "abc\r\ndef")] + public async Task ParseStringSuccess(string input, string expected) + { + int index = 0; + bool success = true; + + string actual = SimpleJson.ParseString(input.ToCharArray(), ref index, ref success); + + Assert.True(success); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("\"abc")] + public async Task ParseStringIncomplete(string input) + { + int index = 0; + bool success = true; + + string actual = SimpleJson.ParseString(input.ToCharArray(), ref index, ref success); + + Assert.False(success); + Assert.Null(actual); + } +} diff --git a/Octokit/Clients/ActionsSelfHostedRunnerGroupsClient.cs b/Octokit/Clients/ActionsSelfHostedRunnerGroupsClient.cs index 5520af6e06..a5bf26fecb 100644 --- a/Octokit/Clients/ActionsSelfHostedRunnerGroupsClient.cs +++ b/Octokit/Clients/ActionsSelfHostedRunnerGroupsClient.cs @@ -203,7 +203,7 @@ public async Task ListAllRunnersForOrganizationRunnerGroup(strin /// The enterprise name /// The runner group id [ManualRoute("GET", "/enterprises/{enterprise}/actions/runner-groups/{runner_group_id}/organizations")] - public Task> ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId) + public Task ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId) { return ListAllRunnerGroupOrganizationsForEnterprise(enterprise, runnerGroupId, ApiOptions.None); } @@ -218,11 +218,16 @@ public Task> ListAllRunnerGroupOrganizationsForEnter /// The runner group id /// Options for changing the API response [ManualRoute("GET", "/enterprises/{enterprise}/actions/runner-groups/{runner_group_id}/organizations")] - public Task> ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options) + public async Task ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options) { Ensure.ArgumentNotNullOrEmptyString(enterprise, nameof(enterprise)); - return ApiConnection.GetAll(ApiUrls.ActionsListEnterpriseRunnerGroupOrganizations(enterprise, runnerGroupId), options); + var results = await ApiConnection.GetAll(ApiUrls.ActionsListEnterpriseRunnerGroupOrganizations(enterprise, runnerGroupId), options).ConfigureAwait(false); + + return new OrganizationsResponse( + results.Count > 0 ? results.Max(x => x.TotalCount) : 0, + results.SelectMany(x => x.Organizations).ToList() + ); } /// @@ -234,7 +239,7 @@ public Task> ListAllRunnerGroupOrganizationsForEnter /// The organization name /// The runner group id [ManualRoute("GET", "/orgs/{org}/actions/runner-groups/{runner_group_id}/repositories")] - public Task> ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId) + public Task ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId) { return ListAllRunnerGroupRepositoriesForOrganization(org, runnerGroupId, ApiOptions.None); } @@ -249,11 +254,16 @@ public Task> ListAllRunnerGroupRepositoriesForOrganiza /// The runner group id /// Options for changing the API response [ManualRoute("GET", "/orgs/{org}/actions/runner-groups/{runner_group_id}/repositories")] - public Task> ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options) + public async Task ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options) { Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); - return ApiConnection.GetAll(ApiUrls.ActionsListOrganizationRunnerGroupRepositories(org, runnerGroupId), options); + var results = await ApiConnection.GetAll(ApiUrls.ActionsListOrganizationRunnerGroupRepositories(org, runnerGroupId), options).ConfigureAwait(false); + + return new RepositoriesResponse( + results.Count > 0 ? results.Max(x => x.TotalCount) : 0, + results.SelectMany(x => x.Repositories).ToList() + ); } } } diff --git a/Octokit/Clients/ActionsWorkflowsClient.cs b/Octokit/Clients/ActionsWorkflowsClient.cs index 7c72e98337..bda62f9ebc 100644 --- a/Octokit/Clients/ActionsWorkflowsClient.cs +++ b/Octokit/Clients/ActionsWorkflowsClient.cs @@ -62,6 +62,41 @@ public Task CreateDispatch(string owner, string name, long workflowId, CreateWor return ApiConnection.Post(ApiUrls.ActionsDispatchWorkflow(owner, name, workflowId), createDispatch); } + /// + /// Manually triggers a GitHub Actions workflow run in a repository by file name. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The workflow file name. + /// The parameters to use to trigger the workflow run. + [ManualRoute("POST", "/repositories/{id}/actions/workflows/{workflow_id}/dispatches")] + public Task CreateDispatch(long repositoryId, string workflowFileName, CreateWorkflowDispatch createDispatch) + { + Ensure.ArgumentNotNullOrEmptyString(workflowFileName, nameof(workflowFileName)); + Ensure.ArgumentNotNull(createDispatch, nameof(createDispatch)); + + return ApiConnection.Post(ApiUrls.ActionsDispatchWorkflow(repositoryId, workflowFileName), createDispatch); + } + + /// + /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The Id of the workflow. + /// The parameters to use to trigger the workflow run. + [ManualRoute("POST", "/repositories/{id}/actions/workflows/{workflow_id}/dispatches")] + public Task CreateDispatch(long repositoryId, long workflowId, CreateWorkflowDispatch createDispatch) + { + Ensure.ArgumentNotNull(createDispatch, nameof(createDispatch)); + + return ApiConnection.Post(ApiUrls.ActionsDispatchWorkflow(repositoryId, workflowId), createDispatch); + } + /// /// Disables a specific workflow in a repository by file name. /// diff --git a/Octokit/Clients/Codespace.cs b/Octokit/Clients/Codespace.cs index 91f1beb4f2..8a44b9a2a9 100644 --- a/Octokit/Clients/Codespace.cs +++ b/Octokit/Clients/Codespace.cs @@ -1,4 +1,5 @@ -using System; +using Octokit.Clients; +using System; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; @@ -23,8 +24,9 @@ public class Codespace public string WebUrl { get; private set; } public string StartUrl { get; private set; } public string StopUrl { get; private set; } + public StringEnum Location { get; private set; } - public Codespace(long id, string name, User owner, User billableOwner, Repository repository, Machine machine, DateTime createdAt, DateTime updatedAt, DateTime lastUsedAt, StringEnum state, string url, string machinesUrl, string webUrl, string startUrl, string stopUrl) + public Codespace(long id, string name, User owner, User billableOwner, Repository repository, Machine machine, DateTime createdAt, DateTime updatedAt, DateTime lastUsedAt, StringEnum state, string url, string machinesUrl, string webUrl, string startUrl, string stopUrl, StringEnum location) { Id = id; Name = name; @@ -41,6 +43,7 @@ public Codespace(long id, string name, User owner, User billableOwner, Repositor WebUrl = webUrl; StartUrl = startUrl; StopUrl = stopUrl; + Location = location; } public Codespace() { } diff --git a/Octokit/Clients/CodespacesClient.cs b/Octokit/Clients/CodespacesClient.cs index afe47ce8ad..23c0b9e41d 100644 --- a/Octokit/Clients/CodespacesClient.cs +++ b/Octokit/Clients/CodespacesClient.cs @@ -76,5 +76,29 @@ public Task Stop(string codespaceName) { return ApiConnection.Post(ApiUrls.CodespaceStop(codespaceName)); } + + /// + /// Returns available machines for the specified repository. + /// + [ManualRoute("GET", "/repos/{repoOwner}/{repoName}/machines")] + public Task GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null) + { + Ensure.ArgumentNotNullOrEmptyString(repoOwner, nameof(repoOwner)); + Ensure.ArgumentNotNullOrEmptyString(repoName, nameof(repoName)); + + return ApiConnection.Get(ApiUrls.GetAvailableMachinesForRepo(repoOwner, repoName, reference)); + } + + /// + /// Creates a new codespace for the authenticated user. + /// + [ManualRoute("POST", "/repos/{owner}/{repo}/codespaces")] + public Task Create(string owner, string repo, NewCodespace newCodespace) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repo, nameof(repo)); + + return ApiConnection.Post(ApiUrls.CreateCodespace(owner, repo), newCodespace); + } } } diff --git a/Octokit/Clients/IActionsSelfHostedRunnerGroupsClient.cs b/Octokit/Clients/IActionsSelfHostedRunnerGroupsClient.cs index 6e419407b3..5007467795 100644 --- a/Octokit/Clients/IActionsSelfHostedRunnerGroupsClient.cs +++ b/Octokit/Clients/IActionsSelfHostedRunnerGroupsClient.cs @@ -119,7 +119,7 @@ public interface IActionsSelfHostedRunnerGroupsClient /// /// The enterprise name /// The runner group id - Task> ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId); + Task ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId); /// /// List organization access to a self-hosted runner group in an enterprise @@ -130,7 +130,7 @@ public interface IActionsSelfHostedRunnerGroupsClient /// The enterprise name /// The runner group id /// Options for changing the API response - Task> ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options); + Task ListAllRunnerGroupOrganizationsForEnterprise(string enterprise, long runnerGroupId, ApiOptions options); /// /// List repository access to a self-hosted runner group in an organization @@ -140,7 +140,7 @@ public interface IActionsSelfHostedRunnerGroupsClient /// /// The organization name /// The runner group id - Task> ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId); + Task ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId); /// /// List repository access to a self-hosted runner group in an organization @@ -151,6 +151,6 @@ public interface IActionsSelfHostedRunnerGroupsClient /// The organization name /// The runner group id /// Options for changing the API response - Task> ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options); + Task ListAllRunnerGroupRepositoriesForOrganization(string org, long runnerGroupId, ApiOptions options); } } diff --git a/Octokit/Clients/IActionsWorkflowsClient.cs b/Octokit/Clients/IActionsWorkflowsClient.cs index 24f0ee791f..0652b8676c 100644 --- a/Octokit/Clients/IActionsWorkflowsClient.cs +++ b/Octokit/Clients/IActionsWorkflowsClient.cs @@ -11,7 +11,7 @@ namespace Octokit public interface IActionsWorkflowsClient { /// - /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// Manually triggers a GitHub Actions workflow run in a repository by slug. /// /// /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event @@ -23,7 +23,7 @@ public interface IActionsWorkflowsClient Task CreateDispatch(string owner, string name, string workflowFileName, CreateWorkflowDispatch createDispatch); /// - /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// Manually triggers a GitHub Actions workflow run in a repository by slug. /// /// /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event @@ -34,6 +34,28 @@ public interface IActionsWorkflowsClient /// The parameters to use to trigger the workflow run. Task CreateDispatch(string owner, string name, long workflowId, CreateWorkflowDispatch createDispatch); + /// + /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The workflow file name. + /// The parameters to use to trigger the workflow run. + Task CreateDispatch(long repositoryId, string workflowFileName, CreateWorkflowDispatch createDispatch); + + /// + /// Manually triggers a GitHub Actions workflow run in a repository by Id. + /// + /// + /// https://developer.github.com/v3/actions/workflows/#create-a-workflow-dispatch-event + /// + /// The Id of the repository. + /// The Id of the workflow. + /// The parameters to use to trigger the workflow run. + Task CreateDispatch(long repositoryId, long workflowId, CreateWorkflowDispatch createDispatch); + /// /// Disables a specific workflow in a repository by Id. /// diff --git a/Octokit/Clients/ICodespacesClient.cs b/Octokit/Clients/ICodespacesClient.cs index a3c630d5b7..e26ed864ca 100644 --- a/Octokit/Clients/ICodespacesClient.cs +++ b/Octokit/Clients/ICodespacesClient.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Octokit @@ -10,5 +11,7 @@ public interface ICodespacesClient Task Get(string codespaceName); Task Start(string codespaceName); Task Stop(string codespaceName); + Task GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null); + Task Create(string owner, string repo, NewCodespace newCodespace); } } \ No newline at end of file diff --git a/Octokit/Clients/IMetaClient.cs b/Octokit/Clients/IMetaClient.cs index f4c21714d0..9c7900608f 100644 --- a/Octokit/Clients/IMetaClient.cs +++ b/Octokit/Clients/IMetaClient.cs @@ -10,6 +10,11 @@ namespace Octokit /// public interface IMetaClient { + /// + /// Returns a client to get public keys for validating request signatures. + /// + IPublicKeysClient PublicKeys { get; } + /// /// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation. /// diff --git a/Octokit/Clients/IOAuthClient.cs b/Octokit/Clients/IOAuthClient.cs index c1add05062..0ce00e4648 100644 --- a/Octokit/Clients/IOAuthClient.cs +++ b/Octokit/Clients/IOAuthClient.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Octokit @@ -26,8 +27,9 @@ public interface IOauthClient /// an access token using this method. /// /// + /// /// - Task CreateAccessToken(OauthTokenRequest request); + Task CreateAccessToken(OauthTokenRequest request, CancellationToken concellationToken = default); /// /// Makes a request to initiate the device flow authentication. @@ -37,25 +39,28 @@ public interface IOauthClient /// This request also returns a device verification code that you must use to receive an access token to check the status of user authentication. /// /// + /// /// - Task InitiateDeviceFlow(OauthDeviceFlowRequest request); + Task InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default); /// - /// Makes a request to get an access token using the response from . + /// Makes a request to get an access token using the response from . /// /// /// Will poll the access token endpoint, until the device and user codes expire or the user has successfully authorized the app with a valid user code. /// /// The client Id you received from GitHub when you registered the application. - /// The response you received from + /// The response you received from + /// /// - Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse); + Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken concellationToken = default); /// - /// Makes a request to get an access token using the refresh token returned in . + /// Makes a request to get an access token using the refresh token returned in . /// /// Token renewal request. + /// /// with the new token set. - Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request); + Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken concellationToken = default); } } diff --git a/Octokit/Clients/IPublicKeysClient.cs b/Octokit/Clients/IPublicKeysClient.cs new file mode 100644 index 0000000000..eae756c8d5 --- /dev/null +++ b/Octokit/Clients/IPublicKeysClient.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's meta public keys API. + /// + /// + /// See the Secret scanning documentation for more details. + /// + public interface IPublicKeysClient + { + /// + /// Retrieves public keys for validating request signatures. + /// + /// Thrown when a general API error occurs. + /// An containing public keys for validating request signatures. + Task Get(PublicKeyType keysType); + } +} diff --git a/Octokit/Clients/MachinesCollection.cs b/Octokit/Clients/MachinesCollection.cs new file mode 100644 index 0000000000..af47b65fd3 --- /dev/null +++ b/Octokit/Clients/MachinesCollection.cs @@ -0,0 +1,26 @@ +using Octokit.Internal; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class MachinesCollection + { + public MachinesCollection(IReadOnlyList machines, int count) + { + Machines = machines; + Count = count; + } + + public MachinesCollection() { } + + [Parameter(Key = "total_count")] + public int Count { get; private set; } + [Parameter(Key = "machines")] + public IReadOnlyList Machines { get; private set; } = new List(); + + internal string DebuggerDisplay => string.Format(CultureInfo.CurrentCulture, "MachinesCollection: Count: {0}", Count); + } +} \ No newline at end of file diff --git a/Octokit/Clients/MetaClient.cs b/Octokit/Clients/MetaClient.cs index c683a34bba..52a61f14a6 100644 --- a/Octokit/Clients/MetaClient.cs +++ b/Octokit/Clients/MetaClient.cs @@ -18,8 +18,14 @@ public class MetaClient : ApiClient, IMetaClient public MetaClient(IApiConnection apiConnection) : base(apiConnection) { + PublicKeys = new PublicKeysClient(apiConnection); } + /// + /// Returns a client to manage get public keys for validating request signatures. + /// + public IPublicKeysClient PublicKeys { get; private set; } + /// /// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation. /// diff --git a/Octokit/Clients/OAuthClient.cs b/Octokit/Clients/OAuthClient.cs index 9d051d1716..c8616cdd2b 100644 --- a/Octokit/Clients/OAuthClient.cs +++ b/Octokit/Clients/OAuthClient.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; namespace Octokit @@ -48,7 +49,7 @@ public Uri GetGitHubLoginUrl(OauthLoginRequest request) } [ManualRoute("POST", "/login/oauth/access_token")] - public async Task CreateAccessToken(OauthTokenRequest request) + public async Task CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(request, nameof(request)); @@ -56,12 +57,12 @@ public async Task CreateAccessToken(OauthTokenRequest request) var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); return response.Body; } [ManualRoute("POST", "/login/device/code")] - public async Task InitiateDeviceFlow(OauthDeviceFlowRequest request) + public async Task InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(request, nameof(request)); @@ -69,12 +70,12 @@ public async Task InitiateDeviceFlow(OauthDeviceFlowReq var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); return response.Body; } [ManualRoute("POST", "/login/oauth/access_token")] - public async Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse) + public async Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId)); Ensure.ArgumentNotNull(deviceFlowResponse, nameof(deviceFlowResponse)); @@ -85,9 +86,11 @@ public async Task CreateAccessTokenForDeviceFlow(string clientId, Oa while (true) { + cancellationToken.ThrowIfCancellationRequested(); + var request = new OauthTokenRequestForDeviceFlow(clientId, deviceFlowResponse.DeviceCode); var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); if (response.Body.Error != null) { @@ -103,7 +106,7 @@ public async Task CreateAccessTokenForDeviceFlow(string clientId, Oa throw new ApiException(string.Format(CultureInfo.InvariantCulture, "{0}: {1}\n{2}", response.Body.Error, response.Body.ErrorDescription, response.Body.ErrorUri), null); } - await Task.Delay(TimeSpan.FromSeconds(pollingDelay)); + await Task.Delay(TimeSpan.FromSeconds(pollingDelay), cancellationToken); } else { @@ -113,14 +116,14 @@ public async Task CreateAccessTokenForDeviceFlow(string clientId, Oa } [ManualRoute("POST", "/login/oauth/access_token")] - public async Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request) + public async Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(request, nameof(request)); var endPoint = ApiUrls.OauthAccessToken(); var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); return response.Body; } } diff --git a/Octokit/Clients/PublicKeysClient.cs b/Octokit/Clients/PublicKeysClient.cs new file mode 100644 index 0000000000..cc34141f5d --- /dev/null +++ b/Octokit/Clients/PublicKeysClient.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's public keys API. + /// + /// + /// See the Secret scanning documentation for more details. + /// + public class PublicKeysClient : ApiClient, IPublicKeysClient + { + /// + /// Initializes a new GitHub Meta Public Keys API client. + /// + /// An API connection. + public PublicKeysClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// Retrieves public keys for validating request signatures. + /// + /// Thrown when a general API error occurs. + /// An containing public keys for validating request signatures. + [ManualRoute("GET", "/meta/public_keys/{keysType}")] + public Task Get(PublicKeyType keysType) + { + return ApiConnection.Get(ApiUrls.PublicKeys(keysType)); + } + } +} diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index dc6d12e937..b70ae025b8 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -4833,6 +4833,16 @@ public static Uri Meta() return "meta".FormatUri(); } + /// + /// Returns the that returns meta in + /// response to a GET request. + /// + /// The to meta. + public static Uri PublicKeys(PublicKeyType keysType) + { + return "meta/public_keys/{0}".FormatUri(keysType.ToParameter()); + } + /// /// Returns the that returns all organization credentials in /// response to a GET request. @@ -5042,6 +5052,28 @@ public static Uri ActionsDispatchWorkflow(string owner, string repo, string work return "repos/{0}/{1}/actions/workflows/{2}/dispatches".FormatUri(owner, repo, workflowFileName.UriEncode()); } + /// + /// Returns the that disables an Actions workflow for a repository. + /// + /// The Id of the repository. + /// The Id of the workflow. + /// The that gets an Actions workflow for a repository. + public static Uri ActionsDispatchWorkflow(long repositoryId, long workflowId) + { + return "repositories/{0}/actions/workflows/{1}/dispatches".FormatUri(repositoryId, workflowId); + } + + /// + /// Returns the that disables an Actions workflow for a repository. + /// + /// The Id of the repository. + /// The workflow file name. + /// The that gets an Actions workflow for a repository. + public static Uri ActionsDispatchWorkflow(long repositoryId, string workflowFileName) + { + return "repositories/{0}/actions/workflows/{1}/dispatches".FormatUri(repositoryId, workflowFileName.UriEncode()); + } + /// /// Returns the that disables an Actions workflow for a repository. /// @@ -5604,6 +5636,22 @@ public static Uri ActionsListOrganizationRunnerGroupRepositories(string org, lon return "orgs/{0}/actions/runner-groups/{1}/repositories".FormatUri(org, runnerGroupId); } + /// + /// Returns the that handles the machine availability for a repository. + /// + /// The owner of the repository. + /// The name of the repository. + /// The reference to check the machine availability for. + public static Uri GetAvailableMachinesForRepo(string owner, string repo, string reference) + { + if (reference is null) + { + return "repos/{0}/{1}/codespaces/machines".FormatUri(owner, repo); + } + + return "repos/{0}/{1}/actions/runners/availability?ref={3}".FormatUri(owner, repo, reference); + } + /// /// Returns the that handles adding or removing of copilot licenses for an organisation /// @@ -5659,6 +5707,11 @@ public static Uri CodespaceStop(string codespaceName) return "user/codespaces/{0}/stop".FormatUri(codespaceName); } + public static Uri CreateCodespace(string owner, string repo) + { + return "repos/{0}/{1}/codespaces".FormatUri(owner, repo); + } + /// /// Returns the that lists the artifacts for a repository. /// diff --git a/Octokit/Models/Common/CodespaceLocation.cs b/Octokit/Models/Common/CodespaceLocation.cs new file mode 100644 index 0000000000..d00d4448a9 --- /dev/null +++ b/Octokit/Models/Common/CodespaceLocation.cs @@ -0,0 +1,16 @@ +using Octokit.Internal; + +namespace Octokit +{ + public enum CodespaceLocation + { + [Parameter(Value = "EuropeWest")] + EuropeWest, + [Parameter(Value = "SoutheastAsia")] + SoutheastAsia, + [Parameter(Value = "UsEast")] + UsEast, + [Parameter(Value = "UsWest")] + UsWest + } +} diff --git a/Octokit/Models/Common/PublicKeyType.cs b/Octokit/Models/Common/PublicKeyType.cs new file mode 100644 index 0000000000..3a8c09a64f --- /dev/null +++ b/Octokit/Models/Common/PublicKeyType.cs @@ -0,0 +1,19 @@ +using Octokit.Internal; + +namespace Octokit +{ + public enum PublicKeyType + { + /// + /// Copilot API public keys for validating request signatures + /// + [Parameter(Value = "copilot_api")] + CopilotApi, + + /// + /// Secret scanning public keys for validating request signatures + /// + [Parameter(Value = "secret_scanning")] + SecretScanning + } +} diff --git a/Octokit/Models/Request/NewCodespace.cs b/Octokit/Models/Request/NewCodespace.cs new file mode 100644 index 0000000000..3be0bf99d9 --- /dev/null +++ b/Octokit/Models/Request/NewCodespace.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class NewCodespace + { + public string MachineType { get; set; } + public string Reference { get; set; } + public CodespaceLocation? Location { get; set; } + public string DisplayName { get; set; } + + public NewCodespace(Machine machineType, string reference = "main", CodespaceLocation? location = null, string displayName = null) + { + MachineType = machineType.Name; + Reference = reference; + Location = location; + DisplayName = displayName; + } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "NewCodespace Repo: {0}", DisplayName); + } + } + + } +} diff --git a/Octokit/Models/Response/MetaPublicKey.cs b/Octokit/Models/Response/MetaPublicKey.cs new file mode 100644 index 0000000000..05b6a86c96 --- /dev/null +++ b/Octokit/Models/Response/MetaPublicKey.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class MetaPublicKey + { + public MetaPublicKey() { } + + public MetaPublicKey(string keyIdentifier, string key, bool isCurrent) + { + KeyIdentifier = keyIdentifier; + Key = key; + IsCurrent = isCurrent; + } + + public string KeyIdentifier { get; protected set; } + + public string Key { get; protected set; } + + public bool IsCurrent { get; protected set; } + + internal string DebuggerDisplay + { + get { return string.Format(CultureInfo.InvariantCulture, "KeyIdentifier: {0} IsCurrent: {1}", KeyIdentifier, IsCurrent); } + } + } +} diff --git a/Octokit/Models/Response/MetaPublicKeys.cs b/Octokit/Models/Response/MetaPublicKeys.cs new file mode 100644 index 0000000000..7927ab7a6e --- /dev/null +++ b/Octokit/Models/Response/MetaPublicKeys.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class MetaPublicKeys + { + public MetaPublicKeys() { } + + public MetaPublicKeys(IReadOnlyList publicKeys) + { + PublicKeys = publicKeys; + } + + public IReadOnlyList PublicKeys { get; protected set; } + + internal string DebuggerDisplay + { + get { return string.Format(CultureInfo.InvariantCulture, "PublicKeys: {0}", PublicKeys.Count); } + } + } +} diff --git a/Octokit/Models/Response/OrganizationsResponse.cs b/Octokit/Models/Response/OrganizationsResponse.cs new file mode 100644 index 0000000000..3f4bbbf19b --- /dev/null +++ b/Octokit/Models/Response/OrganizationsResponse.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OrganizationsResponse + { + public OrganizationsResponse() + { + } + + public OrganizationsResponse(int totalCount, IReadOnlyList organizations) + { + TotalCount = totalCount; + Organizations = organizations; + } + + /// + /// The total number of organizations + /// + public int TotalCount { get; private set; } + + /// + /// The retrieved organizations + /// + public IReadOnlyList Organizations { get; private set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.CurrentCulture, "TotalCount: {0}, Organizations: {1}", TotalCount, Organizations.Count); + } +} diff --git a/Octokit/Models/Response/RepositoriesResponse.cs b/Octokit/Models/Response/RepositoriesResponse.cs index cf17e710b2..69ef9e3b68 100644 --- a/Octokit/Models/Response/RepositoriesResponse.cs +++ b/Octokit/Models/Response/RepositoriesResponse.cs @@ -18,12 +18,12 @@ public RepositoriesResponse(int totalCount, IReadOnlyList repositori } /// - /// The total number of check suites that match the request filter + /// The total number of repositories /// public int TotalCount { get; private set; } /// - /// The retrieved check suites + /// The retrieved repositories /// public IReadOnlyList Repositories { get; private set; } diff --git a/Octokit/SimpleJson.cs b/Octokit/SimpleJson.cs index 7b9a855481..bafb26f921 100644 --- a/Octokit/SimpleJson.cs +++ b/Octokit/SimpleJson.cs @@ -792,15 +792,18 @@ static object ParseValue(char[] json, ref int index, ref bool success) return null; } - static string ParseString(char[] json, ref int index, ref bool success) + internal static string ParseString(char[] json, ref int index, ref bool success) { - StringBuilder s = new StringBuilder(BUILDER_CAPACITY); + // Avoid allocating this StringBuilder unless a backslash is encountered in the json + StringBuilder s = null; char c; EatWhitespace(json, ref index); // " c = json[index++]; + + int startIndex = index; bool complete = false; while (!complete) { @@ -815,6 +818,13 @@ static string ParseString(char[] json, ref int index, ref bool success) } else if (c == '\\') { + if (s == null) + { + s = new StringBuilder(BUILDER_CAPACITY); + for (int i = startIndex; i < index - 1; i++) + s.Append(json[i]); + } + if (index == json.Length) break; c = json[index++]; @@ -875,14 +885,21 @@ static string ParseString(char[] json, ref int index, ref bool success) } } else - s.Append(c); + { + if (s != null) + s.Append(c); + } } if (!complete) { success = false; return null; } - return s.ToString(); + + if (s != null) + return s.ToString(); + + return new string(json, startIndex, index - startIndex - 1); } private static string ConvertFromUtf32(int utf32) diff --git a/build/Build.csproj b/build/Build.csproj index a719b91b10..ac858f4788 100644 --- a/build/Build.csproj +++ b/build/Build.csproj @@ -9,7 +9,7 @@ - +