Skip to content

Commit 2144dc9

Browse files
authored
Wait for the repositories to be created (#128)
1 parent d9ffa46 commit 2144dc9

6 files changed

Lines changed: 110 additions & 27 deletions

File tree

CloneDevOpsTemplate/IServices/IRepositoryService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ public interface IRepositoryService
88
Task<Repositories?> GetRepositoriesAsync(Guid projectId);
99
Task<Repository?> CreateRepositoryAsync(Guid projectId, string name);
1010
Task<HttpResponseMessage> DeleteRepositoryAsync(Guid projectId, Guid repositoryId);
11-
Task<HttpResponseMessage> CreateImportRequest(Guid projectId, Guid repositoryId, string sourceRepositoryRemoteUrl, Guid serviceEndpointId);
11+
Task<GitImportRequest?> CreateImportRequestAsync(Guid projectId, Guid repositoryId, string sourceRepositoryRemoteUrl, Guid serviceEndpointId);
12+
Task<GitImportRequest?> GetImportRequestAsync(Guid projectId, Guid repositoryId, int importRequestId);
1213
}

CloneDevOpsTemplate/Managers/CloneManager.cs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,47 @@ public async Task CloneTeamsAndSettingsAndBoardsAsync(Project templateProject, P
9595

9696
public async Task CloneRepositoriesAsync(Guid templateProjectId, Guid projectId)
9797
{
98-
Repositories repositories = await _repositoryService.GetRepositoriesAsync(projectId) ?? new();
99-
Repositories templateRepositories = await _repositoryService.GetRepositoriesAsync(templateProjectId) ?? new();
98+
var repositories = await _repositoryService.GetRepositoriesAsync(projectId) ?? new();
99+
var templateRepositories = await _repositoryService.GetRepositoriesAsync(templateProjectId) ?? new();
100100

101101
await Parallel.ForEachAsync(templateRepositories.Value, async (templateRepository, ct) =>
102102
{
103-
Repository repository = await _repositoryService.CreateRepositoryAsync(projectId, templateRepository.Name) ?? new();
104-
ServiceModel serviceModel = await _serviceService.CreateServiceAsync(templateRepository.Name, templateRepository.RemoteUrl, templateRepository.Name, projectId) ?? new();
105-
await _repositoryService.CreateImportRequest(projectId, repository.Id, templateRepository.RemoteUrl, serviceModel.Id);
103+
var repository = repositories.Value.FirstOrDefault(r => r.Name == templateRepository.Name)
104+
?? await _repositoryService.CreateRepositoryAsync(projectId, templateRepository.Name) ?? new();
105+
106+
if (repository.Size > 0) return;
107+
108+
var serviceModel = (await _serviceService.GetServicesAsync(projectId) ?? new())
109+
.Value.FirstOrDefault(s => s.Name == templateRepository.Name)
110+
?? await _serviceService.CreateServiceAsync(templateRepository.Name, templateRepository.RemoteUrl, templateRepository.Name, projectId) ?? new();
111+
112+
var importRequest = await _repositoryService.CreateImportRequestAsync(projectId, repository.Id, templateRepository.RemoteUrl, serviceModel.Id);
113+
await WaitForImportAsync(projectId, repository.Id, importRequest?.ImportRequestId ?? 0);
106114
});
107115

108-
await Parallel.ForEachAsync(repositories.Value, async (repository, ct) =>
116+
await Parallel.ForEachAsync(repositories.Value.Where(r => r.Size == 0), async (repository, ct) =>
109117
{
110118
await _repositoryService.DeleteRepositoryAsync(projectId, repository.Id);
111119
});
112120
}
113121

122+
private async Task WaitForImportAsync(Guid projectId, Guid repositoryId, int importRequestId)
123+
{
124+
var timeout = DateTime.UtcNow.AddMinutes(5);
125+
126+
while (DateTime.UtcNow < timeout)
127+
{
128+
await Task.Delay(1000);
129+
var importRequest = await _repositoryService.GetImportRequestAsync(projectId, repositoryId, importRequestId);
130+
if (importRequest?.Status == GitAsyncOperationStatus.Completed)
131+
{
132+
return;
133+
}
134+
}
135+
136+
throw new TimeoutException("Project creation timed out.");
137+
}
138+
114139
public async Task<Dictionary<Guid, Guid>> CloneTeamsAsync(Project templateProject, Project project)
115140
{
116141
Teams templateTeams = await _teamsService.GetTeamsAsync(templateProject.Id) ?? new();

CloneDevOpsTemplate/Models/Repositories.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Text.Json.Serialization;
2+
13
namespace CloneDevOpsTemplate.Models;
24

35
public class Repositories
@@ -21,11 +23,17 @@ public class Repository
2123
public bool IsInMaintenance { get; set; }
2224
}
2325

24-
public class ImportRequest
26+
public class GitImportRequestBase
2527
{
2628
public ImportRequestParameters Parameters { get; set; } = new();
2729
}
2830

31+
public class GitImportRequest : GitImportRequestBase
32+
{
33+
public int ImportRequestId { get; set; }
34+
public GitAsyncOperationStatus Status { get; set; }
35+
}
36+
2937
public class ImportRequestParameters
3038
{
3139
public GitSource GitSource { get; set; } = new();
@@ -36,4 +44,14 @@ public class ImportRequestParameters
3644
public class GitSource
3745
{
3846
public string Url { get; set; } = string.Empty;
39-
}
47+
}
48+
49+
[JsonConverter(typeof(JsonStringEnumConverter<GitAsyncOperationStatus>))]
50+
public enum GitAsyncOperationStatus
51+
{
52+
Abandoned,
53+
Completed,
54+
Failed,
55+
InProgress,
56+
Queued
57+
}

CloneDevOpsTemplate/Services/RepositoryService.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,26 @@ public Task<HttpResponseMessage> DeleteRepositoryAsync(Guid projectId, Guid repo
2626
return _client.DeleteAsync($"{projectId}/_apis/git/repositories/{repositoryId}?api-version=7.1");
2727
}
2828

29-
public Task<HttpResponseMessage> CreateImportRequest(Guid projectId, Guid repositoryId, string sourceRepositoryRemoteUrl, Guid serviceEndpointId)
29+
public async Task<GitImportRequest?> CreateImportRequestAsync(Guid projectId, Guid repositoryId, string sourceRepositoryRemoteUrl, Guid serviceEndpointId)
3030
{
31-
ImportRequest importRequest = new()
31+
GitImportRequestBase importRequest = new()
3232
{
33-
Parameters = new()
33+
Parameters =
3434
{
35-
GitSource = new()
35+
GitSource =
3636
{
3737
Url = sourceRepositoryRemoteUrl
3838
},
3939
ServiceEndpointId = serviceEndpointId,
4040
DeleteServiceEndpointAfterImportIsDone = true
4141
}
4242
};
43-
return _client.PostAsJsonAsync($"{projectId}/_apis/git/repositories/{repositoryId}/importRequests?api-version=7.1", importRequest);
43+
var result = await _client.PostAsJsonAsync($"{projectId}/_apis/git/repositories/{repositoryId}/importRequests?api-version=7.1", importRequest);
44+
return await result.Content.ReadFromJsonAsync<GitImportRequest>();
45+
}
46+
47+
public Task<GitImportRequest?> GetImportRequestAsync(Guid projectId, Guid repositoryId, int importRequestId)
48+
{
49+
return _client.GetFromJsonAsync<GitImportRequest>($"{projectId}/_apis/git/repositories/{repositoryId}/importRequests/{importRequestId}");
4450
}
4551
}

CloneDevOpsTemplateTest/Managers/CloneManagerTest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ public async Task CloneRepositoriesAsync_ShouldCloneRepositories()
9999

100100
_mockRepositoryService.Setup(s => s.GetRepositoriesAsync(templateProjectId)).ReturnsAsync(templateRepositories);
101101
_mockRepositoryService.Setup(s => s.GetRepositoriesAsync(projectId)).ReturnsAsync(repositories);
102+
_mockRepositoryService.Setup(s => s.GetImportRequestAsync(projectId, It.IsAny<Guid>(), It.IsAny<int>()))
103+
.ReturnsAsync(new GitImportRequest { Status = GitAsyncOperationStatus.InProgress });
104+
_mockRepositoryService.Setup(s => s.GetImportRequestAsync(projectId, It.IsAny<Guid>(), It.IsAny<int>()))
105+
.ReturnsAsync(new GitImportRequest { Status = GitAsyncOperationStatus.Completed });
102106

103107
// Act
104108
await _cloneManager.CloneRepositoriesAsync(templateProjectId, projectId);

CloneDevOpsTemplateTest/Services/RepositoryServiceTest.cs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
using CloneDevOpsTemplate.Services;
1+
using System.Net;
2+
using System.Net.Http.Json;
3+
using System.Text.Json;
24
using CloneDevOpsTemplate.Models;
5+
using CloneDevOpsTemplate.Services;
6+
using Microsoft.Extensions.Configuration;
37
using Moq;
48
using Moq.Protected;
5-
using System.Net;
6-
using System.Net.Http.Json;
79
using MyTestProject.Service.Tests.Common;
8-
using Microsoft.Extensions.Configuration;
910

1011
namespace CloneDevOpsTemplateTest.Services;
1112

@@ -163,13 +164,19 @@ public async Task DeleteRepositoryAsync_DeletesRepository()
163164
}
164165

165166
[Fact]
166-
public async Task CreateImportRequest_CreatesImportRequest()
167+
public async Task CreateImportRequestAsync_ReturnsGitImportRequest()
167168
{
168169
// Arrange
169170
var projectId = Guid.NewGuid();
170171
var repositoryId = Guid.NewGuid();
171172
var sourceRepositoryRemoteUrl = "https://example.com/repo.git";
172173
var serviceEndpointId = Guid.NewGuid();
174+
var gitImportRequest = new GitImportRequest
175+
{
176+
ImportRequestId = 1,
177+
Status = GitAsyncOperationStatus.Completed
178+
};
179+
173180
_httpMessageHandlerMock.Protected()
174181
.Setup<Task<HttpResponseMessage>>(
175182
"SendAsync",
@@ -178,18 +185,40 @@ public async Task CreateImportRequest_CreatesImportRequest()
178185
)
179186
.ReturnsAsync(new HttpResponseMessage
180187
{
181-
StatusCode = HttpStatusCode.OK
188+
StatusCode = HttpStatusCode.OK,
189+
Content = JsonContent.Create(gitImportRequest)
182190
});
183191

184192
// Act
185-
await _repositoryService.CreateImportRequest(projectId, repositoryId, sourceRepositoryRemoteUrl, serviceEndpointId);
193+
var result = await _repositoryService.CreateImportRequestAsync(projectId, repositoryId, sourceRepositoryRemoteUrl, serviceEndpointId);
186194

187195
// Assert
188-
_httpMessageHandlerMock.Protected().Verify(
189-
"SendAsync",
190-
Times.Once(),
191-
ItExpr.IsAny<HttpRequestMessage>(),
192-
ItExpr.IsAny<CancellationToken>()
193-
);
196+
Assert.NotNull(result);
197+
Assert.Equal(gitImportRequest.ImportRequestId, result.ImportRequestId);
198+
Assert.Equal(gitImportRequest.Status, result.Status);
199+
}
200+
201+
[Fact]
202+
public async Task CreateImportRequestAsync_ReturnsNullOnFailure()
203+
{
204+
// Arrange
205+
var projectId = Guid.NewGuid();
206+
var repositoryId = Guid.NewGuid();
207+
var sourceRepositoryRemoteUrl = "https://example.com/repo.git";
208+
var serviceEndpointId = Guid.NewGuid();
209+
210+
_httpMessageHandlerMock.Protected()
211+
.Setup<Task<HttpResponseMessage>>(
212+
"SendAsync",
213+
ItExpr.IsAny<HttpRequestMessage>(),
214+
ItExpr.IsAny<CancellationToken>()
215+
)
216+
.ReturnsAsync(new HttpResponseMessage
217+
{
218+
StatusCode = HttpStatusCode.BadRequest
219+
});
220+
221+
// Act & Assert
222+
await Assert.ThrowsAsync<JsonException>(async () => await _repositoryService.CreateImportRequestAsync(projectId, repositoryId, sourceRepositoryRemoteUrl, serviceEndpointId));
194223
}
195224
}

0 commit comments

Comments
 (0)