Skip to content

Commit 57f837b

Browse files
authored
Merge pull request #751 from fossa-app/functional-tests-for-employee-reports-to
Functional Tests for Employee Reports To
2 parents a25c481 + cdb195f commit 57f837b

6 files changed

Lines changed: 174 additions & 12 deletions

File tree

src/API.Core/Validators/EmployeeManagementCommandValidator.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,12 @@ async Task<bool> VisitAsync(EmployeeId reportsToId, ISet<EmployeeId> visited, Ca
105105
if (!visited.Add(reportsToId))
106106
return false;
107107

108-
var upperManager = await _employeeQueryRepository.GetAsync(reportsToId, cancellationToken).ConfigureAwait(false);
109-
return await upperManager.ReportsToId.MatchAsync(
108+
var upperManager = await _employeeQueryRepository.GetOrNoneAsync(reportsToId, cancellationToken).ConfigureAwait(false);
109+
return await upperManager.MatchAsync(
110+
Some: upperManager => upperManager.ReportsToId.MatchAsync(
110111
Some: upperManagerReportsToId => VisitAsync(upperManagerReportsToId, visited, cancellationToken),
111-
None: () => true).ConfigureAwait(false);
112+
None: () => true),
113+
None: () => false).ConfigureAwait(false);
112114
}
113115
}
114116
}

src/API.Web/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
FROM mcr.microsoft.com/dotnet/aspnet:9.0.8
2-
RUN apt-get update && apt-get upgrade -y && apt-get install -y curl=7.88.1-10+deb12u12 && apt-get clean
2+
RUN apt-get update && apt-get upgrade -y && apt-get install -y curl=7.88.1-10+deb12u14 && apt-get clean
33
WORKDIR /App
44
COPY . ./
55
USER app

tests/API.FunctionalTests/ControllerApis/CompanyLicenseControllerWithSystemLicense.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public CompanyLicenseControllerWithSystemLicense(CustomWebApplicationFactory<Def
2020
[Fact]
2121
public async Task CreateCompanyLicenseWithAdministratorAccessTokenAsync()
2222
{
23-
await _factory.SeedCompanyLicenseAsync("01JBV0GR968WJ25BKJVT8NDXEY.Tenant1.ADMIN1", 4, 10, 2, default);
23+
await _factory.SeedCompanyLicenseAsync("01JBV0GR968WJ25BKJVT8NDXEY.Tenant1.ADMIN1", 10, 4, 2, default);
2424

2525
var client = _factory.CreateClient();
2626
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "01JA1ZJAWF27S0J8Z2VJE7673Y.Tenant1.User1");

tests/API.FunctionalTests/ControllerApis/EmployeeControllerWithSystemLicense.cs

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ public async Task ListCreatedEmployeesAsync()
151151

152152
var employeeCreationResponse = await client.PostAsJsonAsync("/api/1.0/Employee", new EmployeeModificationModel(firstName, lastName, fullName));
153153

154+
if (employeeCreationResponse.StatusCode != HttpStatusCode.OK)
155+
{
156+
_testOutputHelper.WriteLine(await employeeCreationResponse.Content.ReadAsStringAsync());
157+
}
154158
employeeCreationResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
155159

156160
var employeeRetrievalResponse = await client.GetAsync("/api/1.0/Employee");
@@ -238,7 +242,7 @@ public async Task UpdateEmployeeAssignedDepartmentAsync()
238242
{
239243
// Arrange
240244
var client = _factory.CreateClient();
241-
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "01JMV0XC70JH9GC8P9M6SYYYAK.Tenant1.ADMIN420425736");
245+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425736");
242246

243247
var employeeResponse = await client.PostAsJsonAsync("/api/1.0/Employee", new EmployeeModificationModel("First", "Last", "Full Name"));
244248
employeeResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
@@ -272,4 +276,157 @@ public async Task UpdateEmployeeAssignedDepartmentAsync()
272276
updatedEmployee.ShouldNotBeNull();
273277
updatedEmployee.AssignedDepartmentId.ShouldBe(dept2Id);
274278
}
279+
280+
[Fact]
281+
public async Task UpdateEmployee_WithReportsTo_SucceedsAsync()
282+
{
283+
// Arrange
284+
var client = _factory.CreateClient();
285+
var manager = await CreateEmployeeAsync(client, "Manager", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425737");
286+
var employee = await CreateEmployeeAsync(client, "Employee", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425738");
287+
288+
// Act
289+
var updateResponse = await client.PutAsJsonAsync($"/api/1.0/Employees/{employee.Id}", new EmployeeManagementModel(null, null, manager.Id));
290+
updateResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
291+
292+
// Assert
293+
var verifyResponse = await client.GetAsync($"/api/1.0/Employees/{employee.Id}");
294+
verifyResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
295+
var updatedEmployee = await verifyResponse.Content.ReadFromJsonAsync<EmployeeRetrievalModel>();
296+
updatedEmployee.ShouldNotBeNull();
297+
updatedEmployee.ReportsToId.ShouldBe(manager.Id);
298+
}
299+
300+
[Fact]
301+
public async Task DeleteEmployee_WithDirectReport_FailsAsync()
302+
{
303+
// Arrange
304+
var client = _factory.CreateClient();
305+
var manager = await CreateEmployeeAsync(client, "Manager", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425739");
306+
var employee = await CreateEmployeeAsync(client, "Employee", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425740");
307+
var updateResponse = await client.PutAsJsonAsync($"/api/1.0/Employees/{employee.Id}", new EmployeeManagementModel(null, null, manager.Id));
308+
updateResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
309+
310+
// Act
311+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425739");
312+
var deleteResponse = await client.DeleteAsync("/api/1.0/Employee");
313+
314+
// Assert
315+
deleteResponse.StatusCode.ShouldBe(HttpStatusCode.FailedDependency);
316+
}
317+
318+
[Fact]
319+
public async Task UpdateEmployee_WithNonExistentReportsTo_FailsAsync()
320+
{
321+
// Arrange
322+
var client = _factory.CreateClient();
323+
var employee = await CreateEmployeeAsync(client, "Employee", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425741");
324+
325+
// Act
326+
var updateResponse = await client.PutAsJsonAsync($"/api/1.0/Employees/{employee.Id}", new EmployeeManagementModel(null, null, 999999L));
327+
328+
// Assert
329+
updateResponse.StatusCode.ShouldBe(HttpStatusCode.UnprocessableEntity);
330+
}
331+
332+
[Fact]
333+
public async Task UpdateEmployee_WithCyclicalReference_FailsAsync()
334+
{
335+
// Arrange
336+
var client = _factory.CreateClient();
337+
var employee1 = await CreateEmployeeAsync(client, "Employee", "One", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425742");
338+
var employee2 = await CreateEmployeeAsync(client, "Employee", "Two", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425743");
339+
340+
await client.PutAsJsonAsync($"/api/1.0/Employees/{employee2.Id}", new EmployeeManagementModel(null, null, employee1.Id));
341+
342+
// Act
343+
var updateResponse = await client.PutAsJsonAsync($"/api/1.0/Employees/{employee1.Id}", new EmployeeManagementModel(null, null, employee2.Id));
344+
345+
// Assert
346+
updateResponse.StatusCode.ShouldBe(HttpStatusCode.UnprocessableEntity);
347+
}
348+
349+
[Fact]
350+
public async Task UpdateEmployee_WithLongerCyclicalReference_FailsAsync()
351+
{
352+
// Arrange
353+
var client = _factory.CreateClient();
354+
var employee1 = await CreateEmployeeAsync(client, "Employee", "One", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425744");
355+
var employee2 = await CreateEmployeeAsync(client, "Employee", "Two", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425745");
356+
var employee3 = await CreateEmployeeAsync(client, "Employee", "Three", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425746");
357+
358+
await client.PutAsJsonAsync($"/api/1.0/Employees/{employee2.Id}", new EmployeeManagementModel(null, null, employee1.Id));
359+
await client.PutAsJsonAsync($"/api/1.0/Employees/{employee3.Id}", new EmployeeManagementModel(null, null, employee2.Id));
360+
361+
// Act
362+
var updateResponse = await client.PutAsJsonAsync($"/api/1.0/Employees/{employee1.Id}", new EmployeeManagementModel(null, null, employee3.Id));
363+
364+
// Assert
365+
updateResponse.StatusCode.ShouldBe(HttpStatusCode.UnprocessableEntity);
366+
}
367+
368+
[Fact]
369+
public async Task ListEmployees_ByReportsToId_ReturnsFilteredResultsAsync()
370+
{
371+
// Arrange
372+
var client = _factory.CreateClient();
373+
var manager = await CreateEmployeeAsync(client, "Manager", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425747");
374+
var employee1 = await CreateEmployeeAsync(client, "Employee", "One", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425748");
375+
var employee2 = await CreateEmployeeAsync(client, "Employee", "Two", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425749");
376+
await CreateEmployeeAsync(client, "Employee", "Three", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425750");
377+
378+
await client.PutAsJsonAsync($"/api/1.0/Employees/{employee1.Id}", new EmployeeManagementModel(null, null, manager.Id));
379+
await client.PutAsJsonAsync($"/api/1.0/Employees/{employee2.Id}", new EmployeeManagementModel(null, null, manager.Id));
380+
381+
// Act
382+
var response = await client.GetAsync($"/api/1.0/Employees?reportsToId={manager.Id}&pageNumber=1&pageSize=10");
383+
384+
// Assert
385+
response.StatusCode.ShouldBe(HttpStatusCode.OK);
386+
var result = await response.Content.ReadFromJsonAsync<PagingResponseModel<EmployeeRetrievalModel>>();
387+
result.ShouldNotBeNull();
388+
result.Items.Count.ShouldBe(2);
389+
result.Items.All(x => x.ReportsToId == manager.Id).ShouldBeTrue();
390+
}
391+
392+
[Fact]
393+
public async Task ListEmployees_TopLevelOnly_ReturnsFilteredResultsAsync()
394+
{
395+
// Arrange
396+
var client = _factory.CreateClient();
397+
var manager = await CreateEmployeeAsync(client, "Manager", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425751");
398+
var employee = await CreateEmployeeAsync(client, "Employee", "User", "01K4H70V6A2K39JB4NCYPQ07KY.Tenant1.ADMIN420425752");
399+
await client.PutAsJsonAsync($"/api/1.0/Employees/{employee.Id}", new EmployeeManagementModel(null, null, manager.Id));
400+
401+
// Act
402+
var response = await client.GetAsync("/api/1.0/Employees?topLevelOnly=true&pageNumber=1&pageSize=10");
403+
404+
// Assert
405+
response.StatusCode.ShouldBe(HttpStatusCode.OK);
406+
var result = await response.Content.ReadFromJsonAsync<PagingResponseModel<EmployeeRetrievalModel>>();
407+
result.ShouldNotBeNull();
408+
result.Items.All(x => x.ReportsToId == null).ShouldBeTrue();
409+
result.Items.Any(x => x.Id == employee.Id).ShouldBeFalse();
410+
result.Items.Any(x => x.Id == manager.Id).ShouldBeTrue();
411+
}
412+
413+
private async Task<EmployeeRetrievalModel> CreateEmployeeAsync(HttpClient client, string firstName, string lastName, string? token = null)
414+
{
415+
if (token != null)
416+
{
417+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
418+
}
419+
420+
var creationResponse = await client.PostAsJsonAsync("/api/1.0/Employee", new EmployeeModificationModel(firstName, lastName, $"{firstName} {lastName}"));
421+
if (creationResponse.StatusCode != HttpStatusCode.OK)
422+
{
423+
_testOutputHelper.WriteLine(await creationResponse.Content.ReadAsStringAsync());
424+
}
425+
creationResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
426+
427+
var retrievalResponse = await client.GetAsync("/api/1.0/Employee");
428+
var employee = await retrievalResponse.Content.ReadFromJsonAsync<EmployeeRetrievalModel>();
429+
employee.ShouldNotBeNull();
430+
return employee;
431+
}
275432
}

tests/API.FunctionalTests/Repositories/EmployeeMongoEasyRepository.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,13 @@ public Task<bool> HasDependencyOnEmployeeAsync(long employeeId, CancellationToke
6565
public Task<PageResult<EmployeeMongoEntity>> PageAsync(TenantEmployeePageQuery pageQuery, CancellationToken cancellationToken)
6666
{
6767
var allItems = EasyStore.Entities.Values
68-
.Where(x => x.TenantID == pageQuery.TenantId && (string.IsNullOrEmpty(pageQuery.Search)
69-
|| x.FirstName?.Contains(pageQuery.Search, StringComparison.OrdinalIgnoreCase) == true
70-
|| x.LastName?.Contains(pageQuery.Search, StringComparison.OrdinalIgnoreCase) == true
71-
|| x.FullName?.Contains(pageQuery.Search, StringComparison.OrdinalIgnoreCase) == true))
68+
.Where(x => x.TenantID == pageQuery.TenantId
69+
&& (!pageQuery.TopLevelOnly || !x.ReportsToId.HasValue)
70+
&& pageQuery.ReportsToId.Match(reportsToId => x.ReportsToId == reportsToId, true)
71+
&& (string.IsNullOrEmpty(pageQuery.Search)
72+
|| x.FirstName?.Contains(pageQuery.Search, StringComparison.OrdinalIgnoreCase) == true
73+
|| x.LastName?.Contains(pageQuery.Search, StringComparison.OrdinalIgnoreCase) == true
74+
|| x.FullName?.Contains(pageQuery.Search, StringComparison.OrdinalIgnoreCase) == true))
7275
.ToList();
7376

7477
var pageItems = allItems.Skip((pageQuery.Page.Number - 1) * pageQuery.Page.Size).Take(pageQuery.Page.Size).ToList();

tests/API.FunctionalTests/Seed/CompanyLicenseExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ public static async Task SeedCompanyLicensesAsync<TEntryPoint>(
2020
CancellationToken cancellationToken)
2121
where TEntryPoint : class
2222
{
23-
await SeedCompanyLicenseAsync(factory, "01JB0QS2K6SA4KYD8S920W7DMG.Tenant1.ADMIN1", 300, 10, 5, cancellationToken).ConfigureAwait(false);
23+
await SeedCompanyLicenseAsync(factory, "01JB0QS2K6SA4KYD8S920W7DMG.Tenant1.ADMIN1", 300, 200, 100, cancellationToken).ConfigureAwait(false);
2424
await SeedCompanyLicenseAsync(factory, "01JB0RAH24ZJBA53AJF5F5MMZX.Tenant2.ADMIN1", 300, 10, 5, cancellationToken).ConfigureAwait(false);
2525
await SeedCompanyLicenseAsync(factory, "01JB0RAH24ZJBA53AJF5F5MMZX.Tenant3.ADMIN1", 300, 10, 5, cancellationToken).ConfigureAwait(false);
2626
}
2727

2828
public static async Task SeedCompanyLicenseAsync<TEntryPoint>(
2929
this WebApplicationFactory<TEntryPoint> factory,
3030
string accessToken,
31-
int maximumBranchCount,
3231
int maximumEmployeeCount,
32+
int maximumBranchCount,
3333
int maximumDepartmentCount,
3434
CancellationToken cancellationToken)
3535
where TEntryPoint : class

0 commit comments

Comments
 (0)