Skip to content

Commit

Permalink
#81. [Issues] Add statuses, add transition rules.
Browse files Browse the repository at this point in the history
  • Loading branch information
sys27 committed Sep 3, 2024
1 parent 4ec886a commit 5e6d6cc
Show file tree
Hide file tree
Showing 89 changed files with 3,425 additions and 262 deletions.
31 changes: 31 additions & 0 deletions Pyro.Api/Pyro.ApiTests/Clients/IssueClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,35 @@ public async Task DeleteIssue(string repositoryName, int number)

public async Task DeleteComment(string repositoryName, int number, Guid commentId)
=> await Delete($"/api/repositories/{repositoryName}/issues/{number}/comments/{commentId}");

public async Task<IReadOnlyList<IssueStatusResponse>?> GetIssueStatuses(string repositoryName)
=> await Get<IReadOnlyList<IssueStatusResponse>>($"/api/repositories/{repositoryName}/issues/statuses");

public async Task<IssueStatusResponse?> GetIssueStatus(string repositoryName, Guid id)
=> await Get<IssueStatusResponse>($"/api/repositories/{repositoryName}/issues/statuses/{id}");

public async Task<IssueStatusResponse?> CreateIssueStatus(
string repositoryName,
CreateIssueStatusRequest request)
=> await Post<IssueStatusResponse>($"/api/repositories/{repositoryName}/issues/statuses", request);

public async Task<IssueStatusResponse?> UpdateIssueStatus(
string repositoryName,
Guid id,
UpdateIssueStatusRequest request)
=> await Put<IssueStatusResponse>($"/api/repositories/{repositoryName}/issues/statuses/{id}", request);

public async Task DeleteIssueStatus(string repositoryName, Guid id)
=> await Delete($"/api/repositories/{repositoryName}/issues/statuses/{id}");

public async Task<IReadOnlyList<IssueStatusTransitionResponse>?> GetIssueStatusTransitions(string repositoryName)
=> await Get<IReadOnlyList<IssueStatusTransitionResponse>>($"/api/repositories/{repositoryName}/issues/statuses/transitions");

public async Task<IssueStatusTransitionResponse?> CreateIssueStatusTransition(
string repositoryName,
CreateIssueStatusTransitionRequest request)
=> await Post<IssueStatusTransitionResponse>($"/api/repositories/{repositoryName}/issues/statuses/transitions", request);

public async Task DeleteIssueStatusTransition(string repositoryName, Guid id)
=> await Delete($"/api/repositories/{repositoryName}/issues/statuses/transitions/{id}");
}
6 changes: 3 additions & 3 deletions Pyro.Api/Pyro.ApiTests/Tests/GitRepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public async Task Tests()
private async Task<string> CreateGitRepository()
{
var createRequest = new CreateGitRepositoryRequest(
faker.Lorem.Word(),
faker.Random.Hash(),
faker.Lorem.Sentence(),
"master");
var repository = await client.CreateGitRepository(createRequest);
Expand Down Expand Up @@ -121,7 +121,7 @@ private async Task GetRepositories()

private async Task<LabelResponse> CreateLabel(string repositoryName)
{
var request = new CreateLabelRequest(faker.Lorem.Word(), ColorRequest.FromHex(faker.Internet.Color()));
var request = new CreateLabelRequest(faker.Random.Hash(), ColorRequest.FromHex(faker.Internet.Color()));
var label = await client.CreateLabel(repositoryName, request);

Assert.That(label, Is.Not.Null);
Expand All @@ -138,7 +138,7 @@ private async Task<LabelResponse> CreateLabel(string repositoryName)

private async Task<LabelResponse> UpdateLabel(string repositoryName, Guid id)
{
var request = new UpdateLabelRequest(faker.Lorem.Word(), ColorRequest.FromHex(faker.Internet.Color()));
var request = new UpdateLabelRequest(faker.Random.Hash(), ColorRequest.FromHex(faker.Internet.Color()));
var label = await client.UpdateLabel(repositoryName, id, request);

Assert.That(label, Is.Not.Null);
Expand Down
127 changes: 127 additions & 0 deletions Pyro.Api/Pyro.ApiTests/Tests/IssueStatusTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
// Licensed under the GPL-3.0 license. See LICENSE file in the project root for full license information.

using Bogus;
using Pyro.ApiTests.Clients;
using Pyro.Contracts.Requests;
using Pyro.Contracts.Requests.Issues;
using Pyro.Contracts.Responses.Issues;

namespace Pyro.ApiTests.Tests;

public class IssueStatusTests
{
private Faker faker;
private PyroClient pyroClient;
private IssueClient issueClient;
private string repositoryName;

[OneTimeSetUp]
public async Task Setup()
{
faker = new Faker();
pyroClient = new PyroClient(Api.BaseAddress);
issueClient = pyroClient.Share<IssueClient>();
await issueClient.Login();

var createRequest = new CreateGitRepositoryRequest(
faker.Random.Hash(),
faker.Lorem.Sentence(),
"master");
var repository = await pyroClient.CreateGitRepository(createRequest) ??
throw new Exception("Repository not created");

repositoryName = repository.Name;
}

[OneTimeTearDown]
public async Task TearDown()
{
await issueClient.Logout();
issueClient.Dispose();
pyroClient.Dispose();
}

[Test]
public async Task Tests()
{
var status = await CreateIssueStatus();
status = await UpdateIssueStatus(status.Id);
status = await GetIssueStatus(status.Id);

var transition = await CreateIssueStatusTransition(status.Id);
await DeleteIssueStatusTransition(transition.Id);

await DeleteIssueStatus(status.Id);
}

private async Task<IssueStatusResponse> CreateIssueStatus()
{
var request = new CreateIssueStatusRequest(
faker.Random.Hash(),
ColorRequest.FromHex(faker.Internet.Color()));
var status = await issueClient.CreateIssueStatus(repositoryName, request);

Assert.That(status, Is.Not.Null);
Assert.That(status.Name, Is.EqualTo(request.Name));

return status;
}

private async Task<IssueStatusResponse> UpdateIssueStatus(Guid id)
{
var request = new UpdateIssueStatusRequest(
faker.Random.Hash(),
ColorRequest.FromHex(faker.Internet.Color()));
var status = await issueClient.UpdateIssueStatus(repositoryName, id, request);

Assert.That(status, Is.Not.Null);
Assert.That(status.Name, Is.EqualTo(request.Name));

return status;
}

private async Task<IssueStatusResponse> GetIssueStatus(Guid id)
{
var status = await issueClient.GetIssueStatus(repositoryName, id);

Assert.That(status, Is.Not.Null);

return status;
}

private async Task DeleteIssueStatus(Guid id)
{
await issueClient.DeleteIssueStatus(repositoryName, id);

var statuses = await issueClient.GetIssueStatuses(repositoryName);

Assert.That(statuses, Has.None.Matches<IssueStatusResponse>(x => x.Id == id));
}

private async Task<IssueStatusTransitionResponse> CreateIssueStatusTransition(Guid fromId)
{
var toStatus = await CreateIssueStatus();

var request = new CreateIssueStatusTransitionRequest(fromId, toStatus.Id);
var transition = await issueClient.CreateIssueStatusTransition(repositoryName, request);

Assert.That(transition, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(transition.From.Id, Is.EqualTo(fromId));
Assert.That(transition.To.Id, Is.EqualTo(toStatus.Id));
});

return transition;
}

private async Task DeleteIssueStatusTransition(Guid id)
{
await issueClient.DeleteIssueStatusTransition(repositoryName, id);

var transitions = await issueClient.GetIssueStatusTransitions(repositoryName);

Assert.That(transitions, Has.None.Matches<IssueStatusTransitionResponse>(x => x.Id == id));
}
}
81 changes: 61 additions & 20 deletions Pyro.Api/Pyro.ApiTests/Tests/IssueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Pyro.ApiTests.Clients;
using Pyro.Contracts.Requests;
using Pyro.Contracts.Requests.Issues;
using Pyro.Contracts.Responses;
using Pyro.Contracts.Responses.Issues;

namespace Pyro.ApiTests.Tests;

Expand Down Expand Up @@ -34,24 +36,24 @@ public async Task TearDown()
[Test]
public async Task Tests()
{
var name = await CreateRepository();
var number = await CreateIssue(name);
await UpdateIssue(name, number);
await GetIssue(name, number);

var commentId = await CreateComment(name, number);
await UpdateComment(name, number, commentId);
await DeleteComment(name, number, commentId);
await GetCommentsAfterCommentDelete(name, number);

await DeleteIssue(name, number);
await GetIssuesAfterIssueDelete(name, number);
var repositoryName = await CreateRepository();
var number = await CreateIssue(repositoryName);
await UpdateIssue(repositoryName, number);
await GetIssue(repositoryName, number);

var commentId = await CreateComment(repositoryName, number);
await UpdateComment(repositoryName, number, commentId);
await DeleteComment(repositoryName, number, commentId);
await GetCommentsAfterCommentDelete(repositoryName, number);

await DeleteIssue(repositoryName, number);
await GetIssuesAfterIssueDelete(repositoryName, number);
}

private async Task<string> CreateRepository()
{
var createRequest = new CreateGitRepositoryRequest(
faker.Lorem.Word(),
faker.Random.Hash(),
faker.Lorem.Sentence(),
"master");
var repository = await pyroClient.CreateGitRepository(createRequest) ??
Expand All @@ -66,20 +68,59 @@ private async Task<string> CreateRepository()
throw new Exception("Label not created");
}

var statusIds = new List<Guid>(3);
for (var i = 0; i < 3; i++)
{
var createStatusRequest = new CreateIssueStatusRequest(
faker.Random.Hash(),
ColorRequest.FromHex(faker.Internet.Color()));
var status = await issueClient.CreateIssueStatus(repository.Name, createStatusRequest) ??
throw new Exception("Status not created");

foreach (var statusId in statusIds)
{
var createTransitionRequest = new CreateIssueStatusTransitionRequest(statusId, status.Id);
_ = await issueClient.CreateIssueStatusTransition(repository.Name, createTransitionRequest) ??
throw new Exception("Transition not created");

createTransitionRequest = new CreateIssueStatusTransitionRequest(status.Id, statusId);
_ = await issueClient.CreateIssueStatusTransition(repository.Name, createTransitionRequest) ??
throw new Exception("Transition not created");
}

statusIds.Add(status.Id);
}

return repository.Name;
}

private async Task<int> CreateIssue(string repositoryName)
private async Task<LabelResponse> GetRandomLabel(string repositoryName)
{
var labels = await pyroClient.GetLabels(repositoryName);
if (labels is null || labels.Count == 0)
throw new Exception("Labels not found");

var label = new Randomizer().ArrayElement(labels.ToArray());
return new Randomizer().ArrayElement(labels.ToArray());
}

private async Task<IssueStatusResponse> GetRandomStatus(string repositoryName)
{
var statuses = await issueClient.GetIssueStatuses(repositoryName);
if (statuses is null || statuses.Count == 0)
throw new Exception("Statuses not found");

return new Randomizer().ArrayElement(statuses.ToArray());
}

private async Task<int> CreateIssue(string repositoryName)
{
var label = await GetRandomLabel(repositoryName);
var status = await GetRandomStatus(repositoryName);

var createRequest = new CreateIssueRequest(
faker.Lorem.Sentence(),
null,
status.Id,
[label.Id]);
var issue = await issueClient.CreateIssue(repositoryName, createRequest);

Expand All @@ -90,22 +131,21 @@ private async Task<int> CreateIssue(string repositoryName)
Assert.That(issue.Assignee?.Id, Is.EqualTo(createRequest.AssigneeId));
Assert.That(issue.Labels, Has.Count.EqualTo(1));
Assert.That(issue.Labels, Has.One.EqualTo(label));
Assert.That(issue.Status.Id, Is.EqualTo(status.Id));
});

return issue.IssueNumber;
}

private async Task UpdateIssue(string repositoryName, int number)
{
var labels = await pyroClient.GetLabels(repositoryName);
if (labels is null || labels.Count == 0)
throw new Exception("Labels not found");

var label = new Randomizer().ArrayElement(labels.ToArray());
var label = await GetRandomLabel(repositoryName);
var status = await GetRandomStatus(repositoryName);

var updateRequest = new UpdateIssueRequest(
faker.Lorem.Sentence(),
Guid.Parse("F9BA057A-35B0-4D10-8326-702D8F7EC966"),
status.Id,
[label.Id]);
var issue = await issueClient.UpdateIssue(repositoryName, number, updateRequest);

Expand All @@ -116,6 +156,7 @@ private async Task UpdateIssue(string repositoryName, int number)
Assert.That(issue.Assignee?.Id, Is.EqualTo(updateRequest.AssigneeId));
Assert.That(issue.Labels, Has.Count.EqualTo(1));
Assert.That(issue.Labels, Has.One.EqualTo(label));
Assert.That(issue.Status.Id, Is.EqualTo(status.Id));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task SetUp()
await issueClient.Login();

var createRequest = new CreateGitRepositoryRequest(
faker.Lorem.Word(),
faker.Random.Hash(),
faker.Lorem.Sentence(),
"master");
var repository = await pyroClient.CreateGitRepository(createRequest) ??
Expand All @@ -34,16 +34,23 @@ public async Task SetUp()
repositoryName = repository.Name;

var createLabelRequest = new CreateLabelRequest(
faker.Lorem.Word(),
faker.Random.Hash(),
ColorRequest.FromHex(faker.Internet.Color()));
var label = await pyroClient.CreateLabel(repository.Name, createLabelRequest) ??
throw new Exception("Label not created");

labelId = label.Id;

var createIssueStatusRequest = new CreateIssueStatusRequest(
faker.Random.Hash(),
ColorRequest.FromHex(faker.Internet.Color()));
var status = await issueClient.CreateIssueStatus(repository.Name, createIssueStatusRequest) ??
throw new Exception("Status not created");

var createIssueRequest = new CreateIssueRequest(
faker.Lorem.Word(),
faker.Random.Hash(),
null,
status.Id,
[labelId]);
var issue = await issueClient.CreateIssue(repository.Name, createIssueRequest) ??
throw new Exception("Issue not created");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

namespace Pyro.Contracts.Requests.Issues;

public record CreateIssueRequest(string Title, Guid? AssigneeId, IReadOnlyList<Guid> Labels);
public record CreateIssueRequest(string Title, Guid? AssigneeId, Guid StatusId, IReadOnlyList<Guid> Labels);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
// Licensed under the GPL-3.0 license. See LICENSE file in the project root for full license information.

namespace Pyro.Contracts.Requests.Issues;

public record CreateIssueStatusRequest(string Name, ColorRequest Color);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
// Licensed under the GPL-3.0 license. See LICENSE file in the project root for full license information.

namespace Pyro.Contracts.Requests.Issues;

public record CreateIssueStatusTransitionRequest(Guid FromId, Guid ToId);
Loading

0 comments on commit 5e6d6cc

Please sign in to comment.