diff --git a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs index 22f0fb2..4990411 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs @@ -12,6 +12,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory { @@ -27,7 +28,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) services.AddScoped(); // Unique DB name per test class - var dbName = $"TestDb_{GetType().Name}"; + var dbName = $"TestDb_{GetType().Name}_{Guid.NewGuid()}"; services.AddDbContext(options => options.UseInMemoryDatabase(dbName)); @@ -38,6 +39,26 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureDeleted(); // Clean slate db.Database.EnsureCreated(); + + + // Seed the database + db.Venue.Add(new Venue { VenueID = 4, VenueName = "Test Venue" }); + db.Venue.Add(new Venue { VenueID = 99, VenueName = "Venue to Delete" }); + db.Venue.Add(new Venue { VenueID = 98, VenueName = "Second Venue to Delete" }); + + db.RaspData.Add(new RaspData { Id = 998, MacAddress = "79:1C:89:6B:EC:C7", RaspId = 1, Rssi = -90, UnixTimestamp = 1746033900000 }); + db.RaspData.Add(new RaspData { Id = 999, MacAddress = "79:1C:89:6B:EC:C7", RaspId = 1, Rssi = -90, UnixTimestamp = 1746033900000 }); + + db.DetectedDevice.Add(new DetectedDevice { DetectedDeviceId = 999, DeviceX = 50, DeviceY = 70, Timestamp = 1746535200000, VenueID = 4 }); + db.DetectedDevice.Add(new DetectedDevice { DetectedDeviceId = 998, DeviceX = 60, DeviceY = 90, Timestamp = 1746535200000, VenueID = 4 }); + db.DetectedDevice.Add(new DetectedDevice { DetectedDeviceId = 997, DeviceX = 80, DeviceY = 40, Timestamp = 1746535200000, VenueID = 4 }); + + // Adding Raspberry Pi devices to the database + db.RaspberryPi.Add(new RaspberryPi { VenueID = 10, RaspX = 50, RaspY = 60 }); + db.RaspberryPi.Add(new RaspberryPi { VenueID = 10, RaspX = 90, RaspY = 100 }); + db.RaspberryPi.Add(new RaspberryPi { VenueID = 10, RaspX = 50, RaspY = 30 }); + + db.SaveChanges(); }); } } \ No newline at end of file diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/DetectedDevicesIntegrationTests.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/DetectedDevicesIntegrationTests.cs index ed74ad3..43ef331 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/DetectedDevicesIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/DetectedDevicesIntegrationTests.cs @@ -14,7 +14,7 @@ public class DetectedDevicesIntegrationTests : IClassFixture + /// testing GetLatestValidHeatmap endpoint + /// + /// + /// Expected to pass by checking the response code + /// Also if the responseContent is not null because we expect a bitstring + /// [Fact] - public async Task GetHeatmapAtSpecificTime_returnBitstring() + public async Task GetLatestValidHeatmap_returnBitstring() { - // Arrange: Create a Venue object to send to the API - var venue = new Venue { VenueName = "TestVenue" }; - - // Act: Send the POST request to the /api/Venue endpoint - await _client.PostAsJsonAsync("/api/Venue", venue); - - - // Arrange - timestamp -= (timestamp % 30000); // same TimeInterval used in controller - var detectedDevice = new DetectedDevice - { - DeviceX = 50, - DeviceY = 100, - Timestamp = timestamp, - VenueID = venue.VenueID, - }; - - await _client.PostAsJsonAsync("/api/DetectedDevices", detectedDevice); - - var result = await _client.GetAsync($"/api/DetectedDevices/getHeatmapAtSpecificTime/{timestamp}"); + var result = await _client.GetAsync($"api/DetectedDevices/getLatestValidHeatmap"); var responseContent = await result.Content.ReadAsStringAsync(); + result.EnsureSuccessStatusCode(); - // Assert Assert.False(string.IsNullOrWhiteSpace(responseContent)); - _TestOutput.WriteLine(responseContent); } + /// + /// Testing uploadmultiple endpoint by using dummy data from a file + /// + /// + /// Expected to pass by checking the response code + /// Also if the statuscode matches the expected + /// [Fact] public async Task HandleRaspPostRequest_test() { - // Arrange + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Data", "raspOutputData.json"); var raspOutputData = JsonSerializer.Deserialize( @@ -66,26 +59,22 @@ public async Task HandleRaspPostRequest_test() new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); var response = await _client.PostAsJsonAsync("/api/detectedDevices/uploadMultiple", raspOutputData); + response.EnsureSuccessStatusCode(); _TestOutput.WriteLine($"response : {response}"); - - // Assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); } + /// + /// testing get detectedDevice + /// + /// + /// Expected to pass by checking the response code + /// Also if the list of DetectedDevices are not null nor empty + /// [Fact] public async Task CanGetDetectedDevices() { - timestamp -= (timestamp % 30000); // same TimeInterval used in controller - var detectedDevice = new DetectedDevice - { - DeviceX = 50, - DeviceY = 70, - Timestamp = timestamp, - VenueID = 1 - }; - - await _client.PostAsJsonAsync("/api/DetectedDevices", detectedDevice); var response = await _client.GetAsync("/api/DetectedDevices"); response.EnsureSuccessStatusCode(); diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs index aaf8914..bd16275 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs @@ -21,83 +21,24 @@ public RaspDataIntegrationTests(CustomWebApplicationFactory factory) } + /// + /// testing post endpoint for RaspData + /// + /// + /// Expected to pass by checking the response code + /// Also if the returned MacAddress is the same as expected + /// [Fact] public async Task PostRaspData_SavesRaspData_ReturnsCreated() { - // Arrange: Create a Venue object to send to the API var RaspData = new RaspData { MacAddress = "24:58:46:97:75:3F", RaspId = 3, Rssi = -82, UnixTimestamp = 1746530400000 }; - // Act: Send the POST request to the /api/Venue endpoint var response = await _client.PostAsJsonAsync("/api/RaspData", RaspData); - - // Assert: Ensure the response status code is 201 (Created) response.EnsureSuccessStatusCode(); - // Deserialize the returned content to check the saved venue var returned = await response.Content.ReadFromJsonAsync(); - // Assert: Check if the returned venue is as expected Assert.Equal("24:58:46:97:75:3F", returned.MacAddress); } - - /* - [Fact] - public async Task GetVenue__ReturnsVenue() - { - // Arrange - const int id = 4; - var response = await _client.GetAsync($"/api/Venue/{id}"); - response.EnsureSuccessStatusCode(); - var returned = await response.Content.ReadFromJsonAsync(); - - // Assert - Assert.Equal(id, returned.VenueID); - Assert.Equal("TestVenue", returned.VenueName); - } - - [Fact] - public async Task GetVenue_UpdateVenue_ReturnsUpdatedVenue() - { - // Arrange - const int id = 4; - var getResponse = await _client.GetAsync($"/api/Venue/{id}"); - getResponse.EnsureSuccessStatusCode(); - var originalVenue = await getResponse.Content.ReadFromJsonAsync(); - - Assert.NotNull(originalVenue); - - // Act - Modify the venue - originalVenue.VenueName = "Updated Venue Name"; - - var putResponse = await _client.PutAsJsonAsync($"/api/Venue/{id}", originalVenue); - putResponse.EnsureSuccessStatusCode(); - - // Assert - Get again and verify the updated name - var confirmResponse = await _client.GetAsync($"/api/Venue/{id}"); - confirmResponse.EnsureSuccessStatusCode(); - var updatedVenue = await confirmResponse.Content.ReadFromJsonAsync(); - - Assert.Equal("Updated Venue Name", updatedVenue.VenueName); - } - - [Fact] - public async Task DeleteVenue_ReturnsDeletedVenue_And_CannotBeFoundAfter() - { - // Arrange - var venue = new Venue { VenueID = 99, VenueName = "Venue to Delete" }; - var postResponse = await _client.PostAsJsonAsync("/api/Venue", venue); - postResponse.EnsureSuccessStatusCode(); - - // Act - var deleteResponse = await _client.DeleteAsync($"/api/Venue/{venue.VenueID}"); - - // Assert - Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode); - - var getAfterDelete = await _client.GetAsync($"/api/Venue/{venue.VenueID}"); - Assert.Equal(HttpStatusCode.NotFound, getAfterDelete.StatusCode); - } - */ - } } diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs index e6379a6..117e3c3 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs @@ -20,15 +20,22 @@ public VenueIntegrationTests(CustomWebApplicationFactory factory) _client = _factory.CreateClient(); } + /// + /// Testing our endpoint for posting a Venue + /// + /// + /// Expected to pass by checking the response code + /// Also if the returned name is the same as expected + /// [Fact] public async Task PostVenue_SavesVenue_ReturnsCreated() { // Arrange: Create a Venue object to send to the API - var venue = new Venue { VenueID = 4, VenueName = "TestVenue" }; + var venue = new Venue { VenueID = 5, VenueName = "TestVenue" }; // Act: Send the POST request to the /api/Venue endpoint var response = await _client.PostAsJsonAsync("/api/Venue", venue); - + response.EnsureSuccessStatusCode(); // Deserialize the returned content to check the saved venue var returned = await response.Content.ReadFromJsonAsync(); @@ -36,27 +43,42 @@ public async Task PostVenue_SavesVenue_ReturnsCreated() Assert.Equal("TestVenue", returned.VenueName); } + /// + /// testing get endpoint for getting a Venue + /// + /// + /// Expected to pass by checking the response code + /// Also if the returned name and Id is the same as expected + /// [Fact] public async Task GetVenue__ReturnsVenue() { // Arrange const int id = 4; var response = await _client.GetAsync($"/api/Venue/{id}"); - + response.EnsureSuccessStatusCode(); var returned = await response.Content.ReadFromJsonAsync(); // Assert Assert.Equal(id, returned.VenueID); - Assert.Equal("TestVenue", returned.VenueName); + Assert.Equal("Test Venue", returned.VenueName); } + /// + /// Testing the put endpoint by first getting a Venue + /// Then use the enpoint to update its name + /// + /// + /// Expected to pass by checking the response code + /// Also if the returned name is updated as we expected + /// [Fact] public async Task GetVenue_UpdateVenue_ReturnsUpdatedVenue() { // Arrange const int id = 4; var getResponse = await _client.GetAsync($"/api/Venue/{id}"); - + getResponse.EnsureSuccessStatusCode(); var originalVenue = await getResponse.Content.ReadFromJsonAsync(); Assert.NotNull(originalVenue); @@ -65,7 +87,7 @@ public async Task GetVenue_UpdateVenue_ReturnsUpdatedVenue() originalVenue.VenueName = "Updated Venue Name"; var putResponse = await _client.PutAsJsonAsync($"/api/Venue/{id}", originalVenue); - + putResponse.EnsureSuccessStatusCode(); // Assert - Get again and verify the updated name var confirmResponse = await _client.GetAsync($"/api/Venue/{id}"); @@ -75,21 +97,23 @@ public async Task GetVenue_UpdateVenue_ReturnsUpdatedVenue() Assert.Equal("Updated Venue Name", updatedVenue.VenueName); } + /// + /// testing delete endpoint + /// + /// + /// Expected to pass by checking the response code + /// Also if the Venue can not be found after deletion + /// [Fact] public async Task DeleteVenue_ReturnsDeletedVenue_And_CannotBeFoundAfter() { - // Arrange - var venue = new Venue { VenueID = 99, VenueName = "Venue to Delete" }; - var postResponse = await _client.PostAsJsonAsync("/api/Venue", venue); - - // Act - var deleteResponse = await _client.DeleteAsync($"/api/Venue/{venue.VenueID}"); + var deleteResponse = await _client.DeleteAsync("/api/Venue/99"); // Assert Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode); - var getAfterDelete = await _client.GetAsync($"/api/Venue/{venue.VenueID}"); + var getAfterDelete = await _client.GetAsync("/api/Venue/99"); Assert.Equal(HttpStatusCode.NotFound, getAfterDelete.StatusCode); } diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs index 0f2a3b3..e6c6522 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs @@ -30,17 +30,19 @@ public DetectedDeviceHelperTest(CustomWebApplicationFactory factory) _helper = new DetectedDeviceHelper(_context, _circleUtils, null); // Ensure DetectedDeviceHelper is injected } + /// + /// Testing the DetectedDeviceHelper that sorts in our raspData and removes duplicates + /// Using raspOutputData.json which is a dummy data file. + /// + /// + /// Expected to pass by checking if the result is not null. The result is raspData + /// [Fact] public async Task HandleRaspPostRequest_test() { // Arrange var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - // Adding Raspberry Pi devices to the database - _context.RaspberryPi.Add(new RaspberryPi { VenueID = 10, RaspX = 50, RaspY = 60 }); - _context.RaspberryPi.Add(new RaspberryPi { VenueID = 10, RaspX = 90, RaspY = 100 }); - _context.RaspberryPi.Add(new RaspberryPi { VenueID = 10, RaspX = 50, RaspY = 30 }); - await _context.SaveChangesAsync(); // Don't forget to save changes! // Read the raspOutputData from the JSON file // Arrange diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs index c35183b..94e98e4 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs @@ -19,16 +19,21 @@ public class DetectedDevicesControllerTests : IClassFixture(); _controller = new DetectedDevicesController(_context, null); } + /// + /// Creating a detectedDevice + /// + /// + /// Expected to pass by compairng DeviceId and expected DeviceId + /// Also checking its type + /// [Fact] public async Task PostDetectedDevices_ReturnsOkResult() { - // Arrange var detectedDevice = new DetectedDevice { DetectedDeviceId = 1, @@ -38,10 +43,8 @@ public async Task PostDetectedDevices_ReturnsOkResult() Timestamp = 1745562072611 }; - // Act var result = await _controller.PostDetectedDevice(detectedDevice); - // Assert var createdAtActionResult = Assert.IsType(result.Result); var returnedDetectedDevice = Assert.IsType(createdAtActionResult.Value); Assert.Equal(detectedDevice.DetectedDeviceId, returnedDetectedDevice.DetectedDeviceId); diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs index f012fa1..b95a80d 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs @@ -17,19 +17,20 @@ public class RaspDataControllerTests : IClassFixture(); _controller = new RaspDataController(_context); } - /* - * Post RaspData - */ + /// + /// Creating a RaspData + /// + /// + /// Expected to pass by creating a RaspData and checking Id And MacAddress + /// [Fact] public async Task CreateRaspData() { - // Arrange var raspData = new RaspData { MacAddress = "79:1C:89:6B:EC:C7", @@ -38,67 +39,59 @@ public async Task CreateRaspData() UnixTimestamp = 1746033900000 }; - // Act var result = await _controller.PostRaspData(raspData); - // Assert var createdAtActionResult = Assert.IsType(result.Result); var returnedVenue = Assert.IsType(createdAtActionResult.Value); Assert.Equal(raspData.Id, returnedVenue.Id); Assert.Equal(raspData.MacAddress, returnedVenue.MacAddress); } - /* - * Get RaspData by name - */ + /// + /// Getting RaspData by its Id + /// + /// + /// Expected to pass by checking the received MacAddress and expected. Also checking if its a Type of RaspData + /// [Fact] - public async Task GetRaspData_ByName() + public async Task GetRaspData_ById() { - _context.RaspData.Add(new RaspData { Id = 998, MacAddress = "79:1C:89:6B:EC:C7", RaspId = 1, Rssi = -90, UnixTimestamp = 1746033900000 }); - await _context.SaveChangesAsync(); - - - // Act var result = await _controller.GetRaspData(998); - // Assert var actionResult = Assert.IsType>(result); var raspData = Assert.IsType(actionResult.Value); Assert.Equal("79:1C:89:6B:EC:C7", raspData.MacAddress); } - /* - * Delete RaspData by Id - */ + /// + /// Deleting a RaspData instance by its Id and trying to find it + /// + /// + /// Expected to delete a RaspData instance and after that trying to find it to make sure its deleted + /// [Fact] public async Task DeleteRaspData_ById() { - _context.RaspData.Add(new RaspData { Id = 999, MacAddress = "79:1C:89:6B:EC:C7", RaspId = 1, Rssi = -90, UnixTimestamp = 1746033900000 }); - await _context.SaveChangesAsync(); - - // Act - var result = await _controller.DeleteRaspData(999); // Pass the ID of the venue to delete - // Assert + var result = await _controller.DeleteRaspData(999); Assert.IsType(result); - // Assert - var deletedRaspData = await _context.RaspData.FindAsync(3); - Assert.Null(deletedRaspData); // Ensure the venue is no longer in the database + var deletedRaspData = await _context.RaspData.FindAsync(999); + Assert.Null(deletedRaspData); } - /* - * Try to delete a non existing RaspData - */ + /// + /// Trying to delete a non exisiting RaspData + /// + /// + /// Expected to fail because the RaspData instance should not be found + /// [Fact] public async Task DeleteRaspData_RaspDataNotFound_ReturnsNotFound() { - - // Act: Try deleting a non-existing venue var result = await _controller.DeleteRaspData(999); - // Assert: Verify the result is NotFound (404) Assert.IsType(result); } } diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs index 9f68916..ff7b9b7 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs @@ -16,15 +16,18 @@ public class RaspberryPiControllerTests : IClassFixture(); _controller = new RaspberryPiController(_context); } - /* - * Post RaspberryPi - */ + /// + /// Posting a RaspberryPi + /// + /// + /// Expected to pass by creating a Raspberry and checking Id And raspX value + /// Also checking if its has the correct type + /// [Fact] public async Task PostRaspberryPi() { diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs index a209992..93978be 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs @@ -16,31 +16,28 @@ public class VenueControllerTests : IClassFixture private readonly VenueController _controller; public VenueControllerTests(CustomWebApplicationFactory factory) { - // Use the factory to create a scope for the DB context var scope = factory.Services.CreateScope(); _context = scope.ServiceProvider.GetRequiredService(); _controller = new VenueController(_context); - } - /* - * Post venue - */ - + /// + /// Creating a new Venue + /// + /// + /// Expected to pass because it should receive the same name and Id as expected + /// [Fact] public async Task CreateVenue() { - // Arrange var venue = new Venue { - VenueID = 1, // Assuming VenueID is the primary key + VenueID = 1, VenueName = "Test Venue" }; - // Act var result = await _controller.PostVenue(venue); - // Assert var createdAtActionResult = Assert.IsType(result.Result); // Assert it returns CreatedAtActionResult var returnedVenue = Assert.IsType(createdAtActionResult.Value); // Assert the value is a Venue object @@ -48,54 +45,46 @@ public async Task CreateVenue() Assert.Equal(venue.VenueName, returnedVenue.VenueName); // Check if the name matches } - /* - * Get Venue by name - */ + /// + /// Getting a venue by its name + /// + /// + /// Expected to pass because it should receive the same name as expected + /// [Fact] public async Task GetVenue_ByName() { - _context.Venue.Add(new Venue { VenueID = 2, VenueName = "Test Venue" }); - await _context.SaveChangesAsync(); - + var result = await _controller.GetVenue(4); - // Act - var result = await _controller.GetVenue(2); - - // Assert var actionResult = Assert.IsType>(result); var venue = Assert.IsType(actionResult.Value); Assert.Equal("Test Venue", venue.VenueName); } - /* - * Delete Venue by Id - */ + /// + /// Deleting a venue by its ID and trying to find that venue + /// + /// + /// Expected to return null because the venue should not exists + /// [Fact] public async Task DeleteVenue_ById() { - // Arrange: Add a venue to the in-memory database - var venue = new Venue { VenueID = 3, VenueName = "Delete Venue" }; - _context.Venue.Add(venue); - await _context.SaveChangesAsync(); // Save changes to the database - // Act: Call DeleteVenue method - var result = await _controller.DeleteVenue(3); // Pass the ID of the venue to delete + var result = await _controller.DeleteVenue(98); - // Assert: Verify the result is NoContent (204) Assert.IsType(result); - // Assert: Check that the venue was removed from the database - var deletedVenue = await _context.Venue.FindAsync(3); - Assert.Null(deletedVenue); // Ensure the venue is no longer in the database + var deletedVenue = await _context.Venue.FindAsync(98); + Assert.Null(deletedVenue); } [Fact] public async Task DeleteVenue_VenueNotFound_ReturnsNotFound() { - // Act: Try deleting a non-existing venue - var result = await _controller.DeleteVenue(999); // Use a non-existing ID - // Assert: Verify the result is NotFound (404) + var result = await _controller.DeleteVenue(999); + Assert.IsType(result); } } diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/CircleUtilsTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/CircleUtilsTests.cs index 67c91bd..d6552a2 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/CircleUtilsTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/CircleUtilsTests.cs @@ -9,9 +9,13 @@ namespace CrowdedBackend.Tests.UnitTests.Services { public class CircleUtilsTests { - /* - * Test that ensure three RSSI readings and their geometric logic produce a usable position - */ + /// + /// testing to calculatePosition by creating correct points + /// Using the method in CircleUtils and verify its in range + /// + /// + /// Using the method in CircleUtils and verify its in range + /// [Fact] public void CalculatePosition_WithValidData_ReturnsPosition() { @@ -56,13 +60,17 @@ public void CalculatePosition_WithValidData_ReturnsPosition() Assert.InRange(result[0].Y, 3.0, 10.0); } - /* - * Test if 3 overlapping circles has points that exists in 2 circles - */ + /// + /// Correctly finds all pairwise intersection points between three overlapping circles + /// + /// + /// Confirms the result isn't null + /// Confirms that some intersection points were found. + /// Ensures every point knows which two circles created it (ParentIndex has 2 items). + /// [Fact] public void GetIntersectionPoints_WithThreeIntersectingCircles_ReturnsCorrectPoints() { - // Arrange var circleUtils = new CircleUtils(); var circles = new List { @@ -71,30 +79,31 @@ public void GetIntersectionPoints_WithThreeIntersectingCircles_ReturnsCorrectPoi new (2, 4, 5) }; - // Act var result = circleUtils.GetIntersectionPoints(circles); - // Assert Assert.NotNull(result); - Assert.True(result.Count > 0); // Should have some intersection points - Assert.All(result, p => Assert.True(p.ParentIndex.Count == 2)); // Ensure each point has parent indices + Assert.True(result.Count > 0); + Assert.All(result, p => Assert.True(p.ParentIndex.Count == 2)); } - /* - * Test if CricleCirle intersection has two intersections points - */ + + /// + /// Each intersection point’s X and Y coordinates are within reasonable bounds + /// — roughly between the centers of the two circles. + /// + /// + /// Two overlapping circles produce exactly two intersection points. + /// The intersection points fall within expected coordinate ranges. + /// [Fact] public void CircleCircleIntersection_IntersectingCircles_ReturnsTwoPoints() { - // Arrange var circle1 = new Circle(0, 0, 5); var circle2 = new Circle(6, 0, 5); var utils = new CircleUtils(); - // Act var result = utils.CircleCircleIntersection(circle1, circle2); - // Assert Assert.Equal(2, result.Count); Assert.All(result, point => { @@ -104,10 +113,13 @@ public void CircleCircleIntersection_IntersectingCircles_ReturnsTwoPoints() }); } - /* - * Test if CircleCircle intersection has no intersection circles. - * Should return empty list - */ + + /// + /// testing if there is no intersecting circles + /// + /// + /// Should pass because the circles has no intersection + /// [Fact] public void CircleCircleIntersection_NonIntersectingCircles_ReturnsEmptyList() { diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/HeatmapGeneratorTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/HeatmapGeneratorTests.cs index c242a59..09103b8 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/HeatmapGeneratorTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Services/HeatmapGeneratorTests.cs @@ -5,19 +5,21 @@ namespace CrowdedBackend.Tests.UnitTests.Services { public class HeatmapGeneratorTests { - /* - * This test checks if the Generate method produces a valid Base64 string that represents an image. - * We want to ensure that the method outputs something meaningful - */ + + /// + /// This test checks if the Generate method produces a valid Base64 string that represents an image. + /// We want to ensure that the method outputs something meaningful + /// + /// + /// Expected to create an image by checking if the imageBytes.lengths is over 100 + /// [Fact] public void Generate_ReturnsValidBase64String() { - // Arrange var venueName = "testVenue"; var people = new List<(float x, float y)> { (1, 1), (5, 5), (10, 10) }; var raspberries = new List<(float x, float y)> { (0, 0), (6, 6) }; - // Create dummy background file var testImagePath = Path.Combine(Directory.GetCurrentDirectory(), "Services/HeatmapScript", venueName + ".png"); Directory.CreateDirectory(Path.GetDirectoryName(testImagePath)); using (var bmp = new SKBitmap(800, 800)) @@ -26,17 +28,19 @@ public void Generate_ReturnsValidBase64String() bmp.Encode(fs, SKEncodedImageFormat.Png, 100); } - // Act var base64 = HeatmapGenerator.Generate(venueName, raspberries, people); - // Assert Assert.False(string.IsNullOrWhiteSpace(base64)); var imageBytes = Convert.FromBase64String(base64); Assert.True(imageBytes.Length > 100); // Arbitrary sanity check } - /* - * This test checks if the Generate method produces a valid Base64 string that represents an image - */ + + /// + /// testing to generate a valid heatmap with skiasharp + /// + /// + /// Expected to have a density over 0 + /// [Fact] public void Compute2DKDE_ReturnsNonZeroDensity() { @@ -53,10 +57,12 @@ public void Compute2DKDE_ReturnsNonZeroDensity() ); } - /* - * This test ensures that even if the background image doesn't exist - * (i.e., a file is missing), the Generate method still returns a valid image - */ + /// + /// testing to generate a heatmap can be generated even though no background image is found + /// + /// + /// Ensures that the output is not null, empty, or whitespace + /// [Fact] public void Generate_WithMissingBackground_StillReturnsImage() { diff --git a/CrowdedBackend/CrowdedBackend.sln.DotSettings.user b/CrowdedBackend/CrowdedBackend.sln.DotSettings.user index 5bb6fbe..05e0d1c 100644 --- a/CrowdedBackend/CrowdedBackend.sln.DotSettings.user +++ b/CrowdedBackend/CrowdedBackend.sln.DotSettings.user @@ -13,6 +13,7 @@ ForceIncluded ForceIncluded /Users/davidfraenkel/Library/Caches/JetBrains/Rider2024.3/resharper-host/temp/Rider/vAny/CoverageData/_CrowdedBackend.-2054735434/Snapshot/snapshot.utdcvr - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;CrowdedBackend.Tests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Project Location="/Users/davidfraenkel/CrowdedRegionDetection/CrowdedBackend/CrowdedBackend.Tests" Presentation="&lt;CrowdedBackend.Tests&gt;" /> -</SessionState> \ No newline at end of file + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Solution /> +</SessionState> + \ No newline at end of file diff --git a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs index db9e7ea..9e133e7 100644 --- a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs +++ b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs @@ -93,8 +93,8 @@ private async Task> GetDetectedDeviceTimestampHelper(List("/hubs/detecteddevices"); +app.MapHub("/hubs/detecteddevices"); + app.UseHttpLogging(); app.UseHttpsRedirection(); diff --git a/crowdedapp/lib/canteen_pages.dart b/crowdedapp/lib/canteen_pages.dart index cc23056..4f901a6 100644 --- a/crowdedapp/lib/canteen_pages.dart +++ b/crowdedapp/lib/canteen_pages.dart @@ -18,12 +18,13 @@ class _CanteenPageState extends State { HubConnection? _hubConnection; // Track when the image was last updated DateTime? _lastUpdated; - final GlobalKey _futureBuilderKey = GlobalKey(); bool _needsRefresh = false; + Image? _lastImage; @override void initState() { super.initState(); + _needsRefresh = true; // Ensure image loads on first open _initSignalR(); } @@ -36,8 +37,6 @@ class _CanteenPageState extends State { '$backendUrl/hubs/detecteddevices', HttpConnectionOptions( logging: (level, message) => print('SignalR $level: $message'), - skipNegotiation: true, - transport: HttpTransportType.webSockets, ), ) .withAutomaticReconnect() @@ -57,13 +56,16 @@ class _CanteenPageState extends State { } }); }); - _hubConnection?.on('NewDevicesDetected', (arguments) { - print('Received NewDevicesDetected: $arguments'); + _hubConnection?.on('NewDevicesDetected', (arguments) async { + print('SignalR: NewDevicesDetected event received!'); + print('Jeg bliver ikke mounted'); if (mounted) { + print('Jeg skal opdatere heatmap'); setState(() { _needsRefresh = true; - _lastUpdated = DateTime.now(); }); + // Optionally, immediately trigger a fetch so the UI updates as soon as possible + await _fetchLatestHeatmapImage(); } }); } @@ -89,19 +91,28 @@ class _CanteenPageState extends State { // Fetch the latest valid heatmap image from the new endpoint Future _fetchLatestHeatmapImage() async { + // Add a short delay to ensure backend has generated the new heatmap + await Future.delayed(const Duration(milliseconds: 500)); try { final response = await http.get( Uri.parse('$backendUrl/api/DetectedDevices/getLatestValidHeatmap'), ); if (response.statusCode == 200) { final bytes = base64Decode(response.body); - // Update the last updated time + if (bytes.isEmpty) { + print('Error: Received empty image data'); + return null; + } + final image = Image.memory(bytes, fit: BoxFit.contain); if (mounted) { + print('Jeg kommer her ind'); setState(() { + _lastImage = image; _lastUpdated = DateTime.now(); + _needsRefresh = false; }); } - return Image.memory(bytes, fit: BoxFit.contain); + return image; } else { print('Error fetching heatmap: Status \\${response.statusCode}'); return null; @@ -109,12 +120,6 @@ class _CanteenPageState extends State { } catch (e) { print('Error fetching heatmap: $e'); return null; - } finally { - if (_needsRefresh) { - setState(() { - _needsRefresh = false; - }); - } } } @@ -152,7 +157,7 @@ class _CanteenPageState extends State { ), SizedBox(height: 10), Text( - "Here you are able to view the heatmao of the canteen. The Data in updated every 10 seconds.", + "Here you are able to view the heatmap of the canteen. The Data is updated every 10 seconds.", style: TextStyle(fontSize: 16, color: Colors.white70), ), SizedBox(height: 150), @@ -174,22 +179,24 @@ class _CanteenPageState extends State { child: Container( width: 500, height: 500, - color: Colors.grey[300], - child: FutureBuilder( - key: _needsRefresh ? UniqueKey() : _futureBuilderKey, - future: _fetchLatestHeatmapImage(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Error loading heatmap: \\${snapshot.error}', style: TextStyle(color: Colors.red))); - } else if (snapshot.hasData && snapshot.data != null) { - return snapshot.data!; - } else { - return Icon(Icons.error_outline, size: 200, color: Colors.red); - } - }, - ), + color: Colors.transparent, + child: _needsRefresh + ? FutureBuilder( + key: UniqueKey(), + future: _fetchLatestHeatmapImage(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error loading heatmap: \\${snapshot.error}', style: TextStyle(color: Colors.red))); + } else if (snapshot.hasData && snapshot.data != null) { + return snapshot.data!; + } else { + return Icon(Icons.error_outline, size: 200, color: Colors.red); + } + }, + ) + : (_lastImage ?? Icon(Icons.restaurant, size: 200, color: Colors.black54)), ), ), ), diff --git a/crowdedapp/pubspec.lock b/crowdedapp/pubspec.lock index 90dcc5d..70eea6f 100644 --- a/crowdedapp/pubspec.lock +++ b/crowdedapp/pubspec.lock @@ -199,10 +199,10 @@ packages: dependency: "direct main" description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_parser: dependency: transitive description: diff --git a/crowdedapp/test/canteen_loading_test.dart b/crowdedapp/test/canteen_loading_test.dart deleted file mode 100644 index 17a5340..0000000 --- a/crowdedapp/test/canteen_loading_test.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:crowdedapp/canteen_pages.dart'; -import 'package:flutter/material.dart'; - -void main() { - testWidgets('CanteenPage shows loading indicator', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp(home: CanteenPage(title: 'Canteen View'))); - - // Should show a CircularProgressIndicator while loading - expect(find.byType(CircularProgressIndicator), findsOneWidget); - }); -} \ No newline at end of file diff --git a/crowdedapp/test/canteen_page_render_test.dart b/crowdedapp/test/canteen_page_render_test.dart new file mode 100644 index 0000000..ddb9372 --- /dev/null +++ b/crowdedapp/test/canteen_page_render_test.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:crowdedapp/canteen_pages.dart'; +import 'package:flutter/material.dart'; + +void main() { + testWidgets('CanteenPage renders without crashing', (WidgetTester tester) async { + await tester.runAsync(() async { + await tester.pumpWidget(const MaterialApp(home: CanteenPage(title: 'Canteen View'))); + // Wait for any pending timers to complete (simulate enough time for all timers) + await Future.delayed(const Duration(seconds: 6)); + await tester.pumpAndSettle(); + // Just check that the widget tree contains CanteenPage + expect(find.byType(CanteenPage), findsOneWidget); + }); + }); +} \ No newline at end of file diff --git a/crowdedapp/test/navigation_test.dart b/crowdedapp/test/navigation_test.dart index 583a5c3..524b90d 100644 --- a/crowdedapp/test/navigation_test.dart +++ b/crowdedapp/test/navigation_test.dart @@ -4,16 +4,18 @@ import 'package:flutter/material.dart'; void main() { testWidgets('Bottom navigation switches pages', (WidgetTester tester) async { - await tester.pumpWidget(const Crowdedapp()); + await tester.runAsync(() async { + await tester.pumpWidget(const Crowdedapp()); - // Home page should be visible - expect(find.text('Welcome to Horizon'), findsOneWidget); + // Home page should be visible + expect(find.text('Welcome to Horizon'), findsOneWidget); - // Tap the Canteen tab - await tester.tap(find.byIcon(Icons.restaurant)); - await tester.pumpAndSettle(); + // Tap the Canteen tab + await tester.tap(find.byIcon(Icons.restaurant)); + await tester.pump(const Duration(seconds: 1)); // Use a fixed pump duration instead of pumpAndSettle - // Canteen page should be visible - expect(find.text('Canteen View'), findsOneWidget); + // Canteen page should be visible + expect(find.text('Canteen View'), findsOneWidget); + }); }); } \ No newline at end of file