From d103622dec9c92a4df07c3f20afd7b2af75fc194 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 30 Apr 2025 10:21:03 +0200 Subject: [PATCH 01/16] Added SignalR I think --- .../Controllers/DetectedDevicesController.cs | 13 ++++++++----- .../Helpers/DetectedDeviceHelper.cs | 16 ++++++++++++---- .../CrowdedBackend/Hubs/DetectedDeviceHub.cs | 8 ++++++++ CrowdedBackend/CrowdedBackend/Program.cs | 6 ++++++ 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 CrowdedBackend/CrowdedBackend/Hubs/DetectedDeviceHub.cs diff --git a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs index 0db23d7..163fb1f 100644 --- a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs +++ b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs @@ -1,8 +1,10 @@ using CrowdedBackend.Helpers; +using CrowdedBackend.Hubs; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using CrowdedBackend.Models; using CrowdedBackend.Services.CalculatePositions; +using Microsoft.AspNetCore.SignalR; using Microsoft.IdentityModel.Tokens; namespace CrowdedBackend.Controllers @@ -14,10 +16,13 @@ public class DetectedDevicesController : ControllerBase private const long TimeInterval = 5 * 60 * 1000; private readonly MyDbContext _context; private DetectedDeviceHelper _detectedDevicesHelper; - public DetectedDevicesController(MyDbContext context) + private readonly IHubContext _hubContext; + + public DetectedDevicesController(MyDbContext context, IHubContext hubContext) { _context = context; - _detectedDevicesHelper = new DetectedDeviceHelper(_context, new CircleUtils()); + _hubContext = hubContext; + _detectedDevicesHelper = new DetectedDeviceHelper(_context, new CircleUtils(), _hubContext); } // GET: api/DetectedDevices @@ -119,9 +124,7 @@ public async Task> PostDetectedDevice(DetectedDevic public async Task> PostDetectedDevices(RaspOutputData raspOutputData) { long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - // Don't record anything not in x min intervals - now -= (now % TimeInterval); - + // Don't record anything not in x min intervals now -= (now % TimeInterval); diff --git a/CrowdedBackend/CrowdedBackend/Helpers/DetectedDeviceHelper.cs b/CrowdedBackend/CrowdedBackend/Helpers/DetectedDeviceHelper.cs index 3c4abf8..f0cae79 100644 --- a/CrowdedBackend/CrowdedBackend/Helpers/DetectedDeviceHelper.cs +++ b/CrowdedBackend/CrowdedBackend/Helpers/DetectedDeviceHelper.cs @@ -1,9 +1,8 @@ -using System.Data; using System.Net; using CrowdedBackend.Models; using CrowdedBackend.Services.CalculatePositions; -using Microsoft.AspNetCore.Http.HttpResults; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using CrowdedBackend.Hubs; using Microsoft.EntityFrameworkCore; namespace CrowdedBackend.Helpers; @@ -12,11 +11,13 @@ public class DetectedDeviceHelper { private readonly MyDbContext _context; private CircleUtils _circleUtils; + private readonly IHubContext _hubContext; - public DetectedDeviceHelper(MyDbContext context, CircleUtils circleUtils) + public DetectedDeviceHelper(MyDbContext context, CircleUtils circleUtils, IHubContext hubContext) { this._context = context; this._circleUtils = circleUtils; + this._hubContext = hubContext; } public async Task HandleRaspPostRequest(RaspOutputData raspOutputData, long now) @@ -82,6 +83,13 @@ await PostRaspData(new RaspData Console.WriteLine(_context); await _context.SaveChangesAsync(); + + // Notify clients + await _hubContext.Clients.All.SendAsync("NewDevicesDetected", new + { + Devices = points.Select(p => new { X = p.X, Y = p.Y, Timestamp = now }) + }); + _circleUtils.WipeData(); await this.WipeRaspData(); } diff --git a/CrowdedBackend/CrowdedBackend/Hubs/DetectedDeviceHub.cs b/CrowdedBackend/CrowdedBackend/Hubs/DetectedDeviceHub.cs new file mode 100644 index 0000000..0b8100f --- /dev/null +++ b/CrowdedBackend/CrowdedBackend/Hubs/DetectedDeviceHub.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.SignalR; + +namespace CrowdedBackend.Hubs +{ + public class DetectedDeviceHub : Hub + { + } +} \ No newline at end of file diff --git a/CrowdedBackend/CrowdedBackend/Program.cs b/CrowdedBackend/CrowdedBackend/Program.cs index 531a1d2..22475be 100644 --- a/CrowdedBackend/CrowdedBackend/Program.cs +++ b/CrowdedBackend/CrowdedBackend/Program.cs @@ -1,3 +1,4 @@ +using CrowdedBackend.Hubs; using CrowdedBackend.Models; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.HttpLogging; @@ -23,8 +24,13 @@ logging.ResponseHeaders.Add("MyCustomResponseHeader"); }); + +builder.Services.AddSignalR(); + var app = builder.Build(); +app.MapHub("/hubs/detecteddevices"); + app.UseHttpLogging(); // Configure the HTTP request pipeline. From a8f0ffa5e52d96972e3f27e8eb18e4ccbc7eedda Mon Sep 17 00:00:00 2001 From: Frederik Date: Thu, 1 May 2025 11:00:59 +0200 Subject: [PATCH 02/16] Dette mangler flere tests for at se om det virker perfekt som det skal, men i teorien burde det virke som intended --- crowdedapp/lib/canteen_pages.dart | 176 ++++++++++++++++++++++++------ crowdedapp/lib/crowded_app.dart | 4 +- crowdedapp/pubspec.lock | 84 +++++++++++++- crowdedapp/pubspec.yaml | 2 + 4 files changed, 227 insertions(+), 39 deletions(-) diff --git a/crowdedapp/lib/canteen_pages.dart b/crowdedapp/lib/canteen_pages.dart index af75ccc..60e5107 100644 --- a/crowdedapp/lib/canteen_pages.dart +++ b/crowdedapp/lib/canteen_pages.dart @@ -2,28 +2,119 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +// Hide ConnectionState from signalr_core to resolve ambiguity +import 'package:signalr_core/signalr_core.dart' hide ConnectionState; + + final String backendUrl = dotenv.env['BackendURL'] ?? 'http://localhost:3000'; -class CanteenPage extends StatelessWidget { +class CanteenPage extends StatefulWidget { final String title; const CanteenPage({super.key, required this.title}); - -Future fetchHeatmapImage(int timestamp) async { - final response = await http.get( - Uri.parse('$backendUrl/api/DetectedDevices/getHeatmapAtSpecificTime/$timestamp'), - ); - try { - if (response.statusCode == 200) { - final bytes = base64Decode(response.body); - return Image.memory(bytes, fit: BoxFit.contain); - } else { - throw Exception('Failed to load heatmap image'); + @override + State createState() => _CanteenPageState(); +} + +class _CanteenPageState extends State { + HubConnection? _hubConnection; + int? _latestTimestamp; + // Add key for FutureBuilder to force refresh + final GlobalKey _futureBuilderKey = GlobalKey(); + // Track if we need to refresh + bool _needsRefresh = false; + + @override + void initState() { + super.initState(); + _initSignalR(); + _fetchLatestTimestamp(); + } + + Future _initSignalR() async { + try { + _hubConnection = HubConnectionBuilder() + .withUrl( + '$backendUrl/hubs/detecteddevices', + HttpConnectionOptions( + logging: (level, message) => print('SignalR $level: $message'), + skipNegotiation: true, + transport: HttpTransportType.webSockets, + ), + ) + .withAutomaticReconnect() + .build(); + + _hubConnection?.onclose((error) => + print('Connection closed: ${error?.toString() ?? "No error"}')); + + _hubConnection?.on('NewDevicesDetected', (arguments) { + print('Received NewDevicesDetected: $arguments'); + // The backend sends: { Devices: [{ X, Y, Timestamp }] } + if (arguments != null && arguments.isNotEmpty) { + final devices = arguments[0]['Devices'] as List; + if (devices.isNotEmpty) { + final newTimestamp = devices[0]['Timestamp'] as int; + // Only update state if the timestamp actually changed + if (newTimestamp != _latestTimestamp) { + setState(() { + _latestTimestamp = newTimestamp; + // Force refresh by setting flag and updating state + _needsRefresh = true; + }); + } + } + } + }); + + print('Starting connection to SignalR hub...'); + await _hubConnection?.start(); + print('Connected to SignalR hub successfully!'); + } catch (e) { + print('Error initializing SignalR: $e'); + } + } + + // Renamed to better reflect its purpose + Future _fetchLatestTimestamp() async { + // In a real app, you might fetch the absolute latest timestamp from an endpoint + // For now, just setting it to now to trigger the initial FutureBuilder load + final now = DateTime.now().millisecondsSinceEpoch; + setState(() { + _latestTimestamp = now; + }); + } + + // Modified to return Future for FutureBuilder + Future _fetchHeatmapImage(int timestamp) async { + try { + final response = await http.get( + Uri.parse('$backendUrl/api/DetectedDevices/getHeatmapAtSpecificTime/$timestamp'), + ); + if (response.statusCode == 200) { + final bytes = base64Decode(response.body); + return Image.memory(bytes, fit: BoxFit.contain); + } else { + print('Error fetching heatmap: Status ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error fetching heatmap: $e'); + return null; + } finally { + // Reset the refresh flag after fetch attempt + if (_needsRefresh) { + setState(() { + _needsRefresh = false; + }); + } } - } catch (e) { - print('Error fetching heatmap image: $e'); } - return null; -} + + @override + void dispose() { + _hubConnection?.stop(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -42,40 +133,55 @@ Future fetchHeatmapImage(int timestamp) async { children: [ SizedBox(height: 50), Text( - title, + widget.title, style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white), ), SizedBox(height: 10), Text( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean vel dui diam. Nulla facilisi.", + "Here you are able to view the heatmao of the canteen. The Data in updated every 10 seconds.", style: TextStyle(fontSize: 16, color: Colors.white70), ), SizedBox(height: 150), - Text("Heatmap", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Heatmap", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white)), + // Add timestamp display + if (_latestTimestamp != null) + Text( + "Last updated: ${DateTime.fromMillisecondsSinceEpoch(_latestTimestamp!).toLocal().toString().substring(0, 19)}", + style: TextStyle(fontSize: 14, color: Colors.white70), + ), + ], + ), SizedBox(height: 0), Expanded( child: Center( child: Container( width: 500, - height: 400, + height: 500, color: Colors.grey[300], - child: FutureBuilder( - future: fetchHeatmapImage(1745916000000), // Replace with your timestamp - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } else if (snapshot.hasData && snapshot.data != null) { - return snapshot.data!; - } else { - return Icon(Icons.restaurant, size: 200, color: Colors.black54); - } - }, - ), + child: _latestTimestamp == null + ? Icon(Icons.restaurant, size: 200, color: Colors.black54) + : FutureBuilder( + // Use key to force refresh when _needsRefresh is true + key: _needsRefresh ? UniqueKey() : _futureBuilderKey, + future: _fetchHeatmapImage(_latestTimestamp!), + 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); + } + }, + ), ), ), - ) + ), ], ), ), diff --git a/crowdedapp/lib/crowded_app.dart b/crowdedapp/lib/crowded_app.dart index 5bcc74d..d3b6f0e 100644 --- a/crowdedapp/lib/crowded_app.dart +++ b/crowdedapp/lib/crowded_app.dart @@ -33,8 +33,8 @@ class _CrowdedappState extends State { currentIndex: _currentIndex, onTap: _onItemTapped, backgroundColor: Colors.blue.shade600, - selectedItemColor: Colors.black, - unselectedItemColor: Colors.white, + selectedItemColor: Colors.white, + unselectedItemColor: Colors.black, items: [ BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"), BottomNavigationBarItem(icon: Icon(Icons.restaurant), label: "Canteen"), diff --git a/crowdedapp/pubspec.lock b/crowdedapp/pubspec.lock index 40ff725..90dcc5d 100644 --- a/crowdedapp/pubspec.lock +++ b/crowdedapp/pubspec.lock @@ -129,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" fake_async: dependency: transitive description: @@ -291,6 +299,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" pub_semver: dependency: transitive description: @@ -299,6 +315,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + signalr_core: + dependency: "direct main" + description: + name: signalr_core + sha256: "27c4ce798c8fedc2f7e3e4668c2b1dbcf6ee2a93f40ad24284b5f5bbed84529d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" sky_engine: dependency: transitive description: flutter @@ -320,6 +352,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sse: + dependency: transitive + description: + name: sse + sha256: "4389a01d5bc7ef3e90fbc645f8e7c6d8711268adb1f511e14ae9c71de47ee32b" + url: "https://pub.dev" + source: hosted + version: "4.1.7" + sse_channel: + dependency: transitive + description: + name: sse_channel + sha256: "9aad5d4eef63faf6ecdefb636c0f857bd6f74146d2196087dcf4b17ab5b49b1b" + url: "https://pub.dev" + source: hosted + version: "0.1.1" stack_trace: dependency: transitive description: @@ -360,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" typed_data: dependency: transitive description: @@ -368,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: @@ -396,10 +468,18 @@ packages: dependency: transitive description: name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + url: "https://pub.dev" + source: hosted + version: "2.4.5" yaml: dependency: transitive description: diff --git a/crowdedapp/pubspec.yaml b/crowdedapp/pubspec.yaml index d7e14e6..2af86ed 100644 --- a/crowdedapp/pubspec.yaml +++ b/crowdedapp/pubspec.yaml @@ -31,6 +31,8 @@ dependencies: flutter: sdk: flutter flutter_dotenv: ^5.0.2 + signalr_core: ^1.1.1 + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From 85c7c8a53d17a53900aa06afeb1d25f5125611e9 Mon Sep 17 00:00:00 2001 From: Anders Date: Fri, 2 May 2025 10:22:40 +0200 Subject: [PATCH 03/16] unused lib --- CrowdedBackend/CrowdedBackend/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/CrowdedBackend/CrowdedBackend/Program.cs b/CrowdedBackend/CrowdedBackend/Program.cs index 22475be..bc5d71f 100644 --- a/CrowdedBackend/CrowdedBackend/Program.cs +++ b/CrowdedBackend/CrowdedBackend/Program.cs @@ -2,7 +2,6 @@ using CrowdedBackend.Models; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.HttpLogging; -using Microsoft.Extensions.Http.Logging; var builder = WebApplication.CreateBuilder(args); From 8c024d50d81af5d265981ae66f337e0e1e29da67 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 7 May 2025 10:44:49 +0200 Subject: [PATCH 04/16] endpoints --- .../Controllers/DetectedDevicesController.cs | 27 +++++++++++++++++++ CrowdedBackend/CrowdedBackend/Helpers/log.txt | 0 2 files changed, 27 insertions(+) delete mode 100644 CrowdedBackend/CrowdedBackend/Helpers/log.txt diff --git a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs index 163fb1f..33ef3a4 100644 --- a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs +++ b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs @@ -13,7 +13,11 @@ namespace CrowdedBackend.Controllers [ApiController] public class DetectedDevicesController : ControllerBase { +<<<<<<< Updated upstream private const long TimeInterval = 5 * 60 * 1000; +======= + private const long TimeInterval = 1 * 60 * 1000; +>>>>>>> Stashed changes private readonly MyDbContext _context; private DetectedDeviceHelper _detectedDevicesHelper; private readonly IHubContext _hubContext; @@ -46,6 +50,26 @@ public async Task> GetDetectedDevice(long timestamp) if (detectedDevices.IsNullOrEmpty()) { return Problem("Detected devices is null or empty", statusCode: 500); +<<<<<<< Updated upstream +======= + } + + return await GetDetectedDeviceTimestampHelper(detectedDevices); + } + + // GET: api/DetectedDevices/getLatestValidHeatmap + [HttpGet("getLatestValidHeatmap")] + public async Task> GetLatestValidHeatmap() + { + var detectedDevices = await _context.DetectedDevice + .GroupBy(x => x.Timestamp) + .Select(g => g.OrderByDescending(x => x.Timestamp).First()) + .ToListAsync(); + + if (detectedDevices.IsNullOrEmpty()) + { + return Problem("Detected devices is null or empty", statusCode: 500); +>>>>>>> Stashed changes } List<(float x, float y)> listOfDeviceLocations = []; @@ -124,7 +148,10 @@ public async Task> PostDetectedDevice(DetectedDevic public async Task> PostDetectedDevices(RaspOutputData raspOutputData) { long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); +<<<<<<< Updated upstream +======= +>>>>>>> Stashed changes // Don't record anything not in x min intervals now -= (now % TimeInterval); diff --git a/CrowdedBackend/CrowdedBackend/Helpers/log.txt b/CrowdedBackend/CrowdedBackend/Helpers/log.txt deleted file mode 100644 index e69de29..0000000 From d240d84bc69e3e550c000b040c799fd23d7da3ca Mon Sep 17 00:00:00 2001 From: Frederik Date: Wed, 7 May 2025 10:46:41 +0200 Subject: [PATCH 05/16] nyt endpoint til get image --- crowdedapp/lib/canteen_pages.dart | 165 ++++++++++++++++-------------- 1 file changed, 88 insertions(+), 77 deletions(-) diff --git a/crowdedapp/lib/canteen_pages.dart b/crowdedapp/lib/canteen_pages.dart index 60e5107..cc23056 100644 --- a/crowdedapp/lib/canteen_pages.dart +++ b/crowdedapp/lib/canteen_pages.dart @@ -1,11 +1,10 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -// Hide ConnectionState from signalr_core to resolve ambiguity import 'package:signalr_core/signalr_core.dart' hide ConnectionState; - final String backendUrl = dotenv.env['BackendURL'] ?? 'http://localhost:3000'; class CanteenPage extends StatefulWidget { final String title; @@ -17,91 +16,100 @@ class CanteenPage extends StatefulWidget { class _CanteenPageState extends State { HubConnection? _hubConnection; - int? _latestTimestamp; - // Add key for FutureBuilder to force refresh + // Track when the image was last updated + DateTime? _lastUpdated; final GlobalKey _futureBuilderKey = GlobalKey(); - // Track if we need to refresh bool _needsRefresh = false; @override void initState() { super.initState(); _initSignalR(); - _fetchLatestTimestamp(); } Future _initSignalR() async { try { - _hubConnection = HubConnectionBuilder() - .withUrl( - '$backendUrl/hubs/detecteddevices', - HttpConnectionOptions( - logging: (level, message) => print('SignalR $level: $message'), - skipNegotiation: true, - transport: HttpTransportType.webSockets, - ), - ) - .withAutomaticReconnect() - .build(); - - _hubConnection?.onclose((error) => - print('Connection closed: ${error?.toString() ?? "No error"}')); + print('Initializing SignalR connection to: $backendUrl/hubs/detecteddevices'); + if (_hubConnection == null || _hubConnection?.state == HubConnectionState.disconnected) { + _hubConnection = HubConnectionBuilder() + .withUrl( + '$backendUrl/hubs/detecteddevices', + HttpConnectionOptions( + logging: (level, message) => print('SignalR $level: $message'), + skipNegotiation: true, + transport: HttpTransportType.webSockets, + ), + ) + .withAutomaticReconnect() + .build(); - _hubConnection?.on('NewDevicesDetected', (arguments) { - print('Received NewDevicesDetected: $arguments'); - // The backend sends: { Devices: [{ X, Y, Timestamp }] } - if (arguments != null && arguments.isNotEmpty) { - final devices = arguments[0]['Devices'] as List; - if (devices.isNotEmpty) { - final newTimestamp = devices[0]['Timestamp'] as int; - // Only update state if the timestamp actually changed - if (newTimestamp != _latestTimestamp) { - setState(() { - _latestTimestamp = newTimestamp; - // Force refresh by setting flag and updating state - _needsRefresh = true; - }); + _hubConnection?.onreconnecting((error) { + print('SignalR reconnecting due to error: \\${error?.toString() ?? "Unknown error"}'); + }); + _hubConnection?.onreconnected((connectionId) { + print('SignalR reconnected with connectionId: $connectionId'); + }); + _hubConnection?.onclose((error) { + print('SignalR connection closed: \\${error?.toString() ?? "No error"}'); + Future.delayed(Duration(seconds: 3), () { + if (mounted) { + _initSignalR(); } + }); + }); + _hubConnection?.on('NewDevicesDetected', (arguments) { + print('Received NewDevicesDetected: $arguments'); + if (mounted) { + setState(() { + _needsRefresh = true; + _lastUpdated = DateTime.now(); + }); } - } - }); - - print('Starting connection to SignalR hub...'); - await _hubConnection?.start(); - print('Connected to SignalR hub successfully!'); + }); + } + if (_hubConnection?.state != HubConnectionState.connected) { + print('Starting connection to SignalR hub...'); + await _hubConnection?.start(); + print('Connected to SignalR hub successfully!'); + } } catch (e) { print('Error initializing SignalR: $e'); + if (mounted) { + Future.delayed(Duration(seconds: 5), () { + if (mounted && (_hubConnection == null || + _hubConnection?.state == HubConnectionState.disconnected || + _hubConnection?.state == HubConnectionState.disconnecting)) { + print('Attempting to reconnect after error...'); + _initSignalR(); + } + }); + } } } - // Renamed to better reflect its purpose - Future _fetchLatestTimestamp() async { - // In a real app, you might fetch the absolute latest timestamp from an endpoint - // For now, just setting it to now to trigger the initial FutureBuilder load - final now = DateTime.now().millisecondsSinceEpoch; - setState(() { - _latestTimestamp = now; - }); - } - - // Modified to return Future for FutureBuilder - Future _fetchHeatmapImage(int timestamp) async { + // Fetch the latest valid heatmap image from the new endpoint + Future _fetchLatestHeatmapImage() async { try { final response = await http.get( - Uri.parse('$backendUrl/api/DetectedDevices/getHeatmapAtSpecificTime/$timestamp'), + Uri.parse('$backendUrl/api/DetectedDevices/getLatestValidHeatmap'), ); if (response.statusCode == 200) { final bytes = base64Decode(response.body); + // Update the last updated time + if (mounted) { + setState(() { + _lastUpdated = DateTime.now(); + }); + } return Image.memory(bytes, fit: BoxFit.contain); } else { - print('Error fetching heatmap: Status ${response.statusCode}'); + print('Error fetching heatmap: Status \\${response.statusCode}'); return null; } } catch (e) { print('Error fetching heatmap: $e'); return null; } finally { - // Reset the refresh flag after fetch attempt if (_needsRefresh) { setState(() { _needsRefresh = false; @@ -112,7 +120,13 @@ class _CanteenPageState extends State { @override void dispose() { - _hubConnection?.stop(); + try { + _hubConnection?.stop().catchError((error) { + print("Error stopping connection: $error"); + }); + } catch (e) { + print("Exception while stopping connection: $e"); + } super.dispose(); } @@ -146,10 +160,10 @@ class _CanteenPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Heatmap", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white)), - // Add timestamp display - if (_latestTimestamp != null) + // Show last updated time if available + if (_lastUpdated != null) Text( - "Last updated: ${DateTime.fromMillisecondsSinceEpoch(_latestTimestamp!).toLocal().toString().substring(0, 19)}", + "Last updated: \\${_lastUpdated!.toLocal().toString().substring(0, 19)}", style: TextStyle(fontSize: 14, color: Colors.white70), ), ], @@ -161,24 +175,21 @@ class _CanteenPageState extends State { width: 500, height: 500, color: Colors.grey[300], - child: _latestTimestamp == null - ? Icon(Icons.restaurant, size: 200, color: Colors.black54) - : FutureBuilder( - // Use key to force refresh when _needsRefresh is true - key: _needsRefresh ? UniqueKey() : _futureBuilderKey, - future: _fetchHeatmapImage(_latestTimestamp!), - 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); - } - }, - ), + 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); + } + }, + ), ), ), ), From 90a157004eb149e848851685ef9f144a85ef9a6b Mon Sep 17 00:00:00 2001 From: Frederik Date: Wed, 7 May 2025 10:57:44 +0200 Subject: [PATCH 06/16] anders har givet fix i denne uge --- .../Controllers/DetectedDevicesController.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs index b2a9062..1d44c97 100644 --- a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs +++ b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs @@ -13,7 +13,7 @@ namespace CrowdedBackend.Controllers [ApiController] public class DetectedDevicesController : ControllerBase { - private const long TimeInterval = 30 * 1000; + private const long TimeInterval = 1 * 60 * 1000; private readonly MyDbContext _context; private DetectedDeviceHelper _detectedDevicesHelper; private readonly IHubContext _hubContext; @@ -59,14 +59,12 @@ public async Task> GetLatestValidHeatmap() .GroupBy(x => x.Timestamp) .Select(g => g.OrderByDescending(x => x.Timestamp).First()) .ToListAsync(); - - Console.WriteLine(detectedDevices.Count); - + if (detectedDevices.IsNullOrEmpty()) { return Problem("Detected devices is null or empty", statusCode: 500); } - + return await GetDetectedDeviceTimestampHelper(detectedDevices); } @@ -148,7 +146,7 @@ public async Task> PostDetectedDevice(DetectedDevic public async Task> PostDetectedDevices(RaspOutputData raspOutputData) { long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - + // Don't record anything not in x min intervals now -= (now % TimeInterval); From efb1ddb24d5e1ca8b75c4e453808acc895545105 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 7 May 2025 10:59:02 +0200 Subject: [PATCH 07/16] format whitespace --- .../Controllers/DetectedDevicesController.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs index 33ef3a4..4b8db08 100644 --- a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs +++ b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs @@ -20,8 +20,8 @@ public class DetectedDevicesController : ControllerBase >>>>>>> Stashed changes private readonly MyDbContext _context; private DetectedDeviceHelper _detectedDevicesHelper; - private readonly IHubContext _hubContext; - + private readonly IHubContext _hubContext; + public DetectedDevicesController(MyDbContext context, IHubContext hubContext) { _context = context; @@ -147,12 +147,12 @@ public async Task> PostDetectedDevice(DetectedDevic [HttpPost("uploadMultiple")] public async Task> PostDetectedDevices(RaspOutputData raspOutputData) { - long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); -<<<<<<< Updated upstream - -======= ->>>>>>> Stashed changes - // Don't record anything not in x min intervals + long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); +<<<<<<< Updated upstream + +======= +>>>>>>> Stashed changes + // Don't record anything not in x min intervals now -= (now % TimeInterval); await this._detectedDevicesHelper.HandleRaspPostRequest(raspOutputData, now); From 1a2605a55350d285a0b440bbfe60b4972c494488 Mon Sep 17 00:00:00 2001 From: Frederik Date: Wed, 7 May 2025 15:32:46 +0200 Subject: [PATCH 08/16] =?UTF-8?q?Nu=20virker=20det=20kr=C3=A6ft=C3=A6deme?= =?UTF-8?q?=20:D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crowdedapp/lib/canteen_pages.dart | 68 +++++++++++++++++-------------- crowdedapp/pubspec.lock | 4 +- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/crowdedapp/lib/canteen_pages.dart b/crowdedapp/lib/canteen_pages.dart index cc23056..551d53c 100644 --- a/crowdedapp/lib/canteen_pages.dart +++ b/crowdedapp/lib/canteen_pages.dart @@ -18,12 +18,14 @@ class _CanteenPageState extends State { HubConnection? _hubConnection; // Track when the image was last updated DateTime? _lastUpdated; - final GlobalKey _futureBuilderKey = GlobalKey(); + //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 +38,6 @@ class _CanteenPageState extends State { '$backendUrl/hubs/detecteddevices', HttpConnectionOptions( logging: (level, message) => print('SignalR $level: $message'), - skipNegotiation: true, - transport: HttpTransportType.webSockets, ), ) .withAutomaticReconnect() @@ -57,13 +57,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 +92,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 +121,6 @@ class _CanteenPageState extends State { } catch (e) { print('Error fetching heatmap: $e'); return null; - } finally { - if (_needsRefresh) { - setState(() { - _needsRefresh = false; - }); - } } } @@ -174,22 +180,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: From 9029eea342d983eea1673c0cc1937d403c94a269 Mon Sep 17 00:00:00 2001 From: Frederik Date: Fri, 9 May 2025 11:09:41 +0200 Subject: [PATCH 09/16] fix tests and fixed spelling mistake in app --- crowdedapp/lib/canteen_pages.dart | 3 +-- crowdedapp/test/canteen_loading_test.dart | 12 ------------ crowdedapp/test/navigation_test.dart | 18 ++++++++++-------- 3 files changed, 11 insertions(+), 22 deletions(-) delete mode 100644 crowdedapp/test/canteen_loading_test.dart diff --git a/crowdedapp/lib/canteen_pages.dart b/crowdedapp/lib/canteen_pages.dart index 551d53c..caa075e 100644 --- a/crowdedapp/lib/canteen_pages.dart +++ b/crowdedapp/lib/canteen_pages.dart @@ -18,7 +18,6 @@ class _CanteenPageState extends State { HubConnection? _hubConnection; // Track when the image was last updated DateTime? _lastUpdated; - //final GlobalKey _futureBuilderKey = GlobalKey(); bool _needsRefresh = false; Image? _lastImage; @@ -158,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 heatmao of the canteen. The Data is updated every 10 seconds.", style: TextStyle(fontSize: 16, color: Colors.white70), ), SizedBox(height: 150), 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/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 From 5734dc6b3974f56acb2c107b94a789d8841756a4 Mon Sep 17 00:00:00 2001 From: Frederik Date: Fri, 9 May 2025 11:10:14 +0200 Subject: [PATCH 10/16] =?UTF-8?q?ved=20ikke=20hvorfor=20denne=20ik=20kom?= =?UTF-8?q?=20med=20f=C3=B8r=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crowdedapp/test/canteen_page_render_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 crowdedapp/test/canteen_page_render_test.dart 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 From b4a1c672e9b80948240b7d1451c3148457e696ad Mon Sep 17 00:00:00 2001 From: davidfraenkel Date: Fri, 9 May 2025 11:14:16 +0200 Subject: [PATCH 11/16] Added ensureSuccessStatusCode --- .../IntegrationTests/Controllers/VenueIntegrationTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs index e6379a6..1b2dbdd 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs @@ -56,7 +56,7 @@ public async Task GetVenue_UpdateVenue_ReturnsUpdatedVenue() // Arrange const int id = 4; var getResponse = await _client.GetAsync($"/api/Venue/{id}"); - + var originalVenue = await getResponse.Content.ReadFromJsonAsync(); Assert.NotNull(originalVenue); @@ -65,11 +65,11 @@ 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}"); - + var updatedVenue = await confirmResponse.Content.ReadFromJsonAsync(); Assert.Equal("Updated Venue Name", updatedVenue.VenueName); @@ -81,7 +81,7 @@ 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}"); From d683a96573919b770c6ea455dadcdd622e76badf Mon Sep 17 00:00:00 2001 From: Frederik Date: Fri, 9 May 2025 11:30:42 +0200 Subject: [PATCH 12/16] fix duplicates --- .../Controllers/DetectedDevicesController.cs | 8 -------- CrowdedBackend/CrowdedBackend/Program.cs | 1 - crowdedapp/lib/canteen_pages.dart | 5 ----- 3 files changed, 14 deletions(-) diff --git a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs index 7513701..9e133e7 100644 --- a/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs +++ b/CrowdedBackend/CrowdedBackend/Controllers/DetectedDevicesController.cs @@ -1,12 +1,10 @@ using CrowdedBackend.Helpers; using CrowdedBackend.Hubs; -using CrowdedBackend.Hubs; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using CrowdedBackend.Models; using CrowdedBackend.Services.CalculatePositions; using Microsoft.AspNetCore.SignalR; -using Microsoft.AspNetCore.SignalR; using Microsoft.IdentityModel.Tokens; namespace CrowdedBackend.Controllers @@ -15,22 +13,16 @@ namespace CrowdedBackend.Controllers [ApiController] public class DetectedDevicesController : ControllerBase { - private const long TimeInterval = 1 * 60 * 1000; private const long TimeInterval = 1 * 60 * 1000; private readonly MyDbContext _context; private DetectedDeviceHelper _detectedDevicesHelper; private readonly IHubContext _hubContext; - public DetectedDevicesController(MyDbContext context, IHubContext hubContext) - private readonly IHubContext _hubContext; - public DetectedDevicesController(MyDbContext context, IHubContext hubContext) { _context = context; _hubContext = hubContext; _detectedDevicesHelper = new DetectedDeviceHelper(_context, new CircleUtils(), _hubContext); - _hubContext = hubContext; - _detectedDevicesHelper = new DetectedDeviceHelper(_context, new CircleUtils(), _hubContext); } // GET: api/DetectedDevices diff --git a/CrowdedBackend/CrowdedBackend/Program.cs b/CrowdedBackend/CrowdedBackend/Program.cs index 20cbab4..81eb017 100644 --- a/CrowdedBackend/CrowdedBackend/Program.cs +++ b/CrowdedBackend/CrowdedBackend/Program.cs @@ -1,5 +1,4 @@ using CrowdedBackend.Hubs; -using CrowdedBackend.Hubs; using CrowdedBackend.Models; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.HttpLogging; diff --git a/crowdedapp/lib/canteen_pages.dart b/crowdedapp/lib/canteen_pages.dart index ee54727..caa075e 100644 --- a/crowdedapp/lib/canteen_pages.dart +++ b/crowdedapp/lib/canteen_pages.dart @@ -1,15 +1,11 @@ import 'dart:async'; -import 'dart:async'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:signalr_core/signalr_core.dart' hide ConnectionState; -import 'package:signalr_core/signalr_core.dart' hide ConnectionState; - final String backendUrl = dotenv.env['BackendURL'] ?? 'http://localhost:3000'; -class CanteenPage extends StatefulWidget { class CanteenPage extends StatefulWidget { final String title; const CanteenPage({super.key, required this.title}); @@ -156,7 +152,6 @@ class _CanteenPageState extends State { children: [ SizedBox(height: 50), Text( - widget.title, widget.title, style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white), ), From b7a45643dc9385f157bac0de704f32b368fca8f6 Mon Sep 17 00:00:00 2001 From: Frederik Date: Fri, 9 May 2025 11:39:56 +0200 Subject: [PATCH 13/16] quick fix --- crowdedapp/lib/canteen_pages.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crowdedapp/lib/canteen_pages.dart b/crowdedapp/lib/canteen_pages.dart index caa075e..4f901a6 100644 --- a/crowdedapp/lib/canteen_pages.dart +++ b/crowdedapp/lib/canteen_pages.dart @@ -157,7 +157,7 @@ class _CanteenPageState extends State { ), SizedBox(height: 10), Text( - "Here you are able to view the heatmao of the canteen. The Data is 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), From b86ed09163d22be0afc09c7d51167d5a5c029e5b Mon Sep 17 00:00:00 2001 From: davidfraenkel Date: Fri, 9 May 2025 17:22:50 +0200 Subject: [PATCH 14/16] Seeded the database before testing --- .../CustomWebApplicationFactory.cs | 24 ++++++++- .../DetectedDevicesIntegrationTests.cs | 49 ++++--------------- .../Controllers/RaspDataIntegrationTests.cs | 12 ++--- .../Controllers/VenueIntegrationTests.cs | 19 +++---- .../Helpers/DetectedDeviceHelperTest.cs | 5 -- .../DetectedDevicesControllerTests.cs | 8 +-- .../Controllers/RaspDataControllerTests.cs | 37 +++----------- .../Controllers/RaspberryPiControllerTests.cs | 1 - .../Controllers/VenueControllerTests.cs | 47 ++++++------------ 9 files changed, 66 insertions(+), 136 deletions(-) diff --git a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs index 22f0fb2..61c3f25 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs @@ -10,8 +10,10 @@ namespace CrowdedBackend.Tests; public class CustomWebApplicationFactory : WebApplicationFactory { + private long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); protected override void ConfigureWebHost(IWebHostBuilder builder) { + builder.UseEnvironment("Testing"); builder.ConfigureServices(services => { @@ -27,7 +29,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 +40,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..22e65a0 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( @@ -66,27 +46,16 @@ 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); } [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..59f2e55 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs @@ -24,19 +24,13 @@ public RaspDataIntegrationTests(CustomWebApplicationFactory factory) [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); } diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs index 1b2dbdd..71aa0d9 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs @@ -24,11 +24,11 @@ public VenueIntegrationTests(CustomWebApplicationFactory factory) 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(); @@ -42,12 +42,12 @@ 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); } [Fact] @@ -56,7 +56,7 @@ 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); @@ -78,18 +78,13 @@ public async Task GetVenue_UpdateVenue_ReturnsUpdatedVenue() [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}"); + 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..230d803 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs @@ -36,11 +36,6 @@ 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..aa3ad29 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs @@ -19,7 +19,6 @@ public class DetectedDevicesControllerTests : IClassFixture(); _controller = new DetectedDevicesController(_context, null); @@ -28,7 +27,6 @@ public DetectedDevicesControllerTests(CustomWebApplicationFactory factory) [Fact] public async Task PostDetectedDevices_ReturnsOkResult() { - // Arrange var detectedDevice = new DetectedDevice { DetectedDeviceId = 1, @@ -37,11 +35,9 @@ public async Task PostDetectedDevices_ReturnsOkResult() DeviceY = 4, 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..a540048 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs @@ -22,14 +22,10 @@ public RaspDataControllerTests(CustomWebApplicationFactory factory) _context = scope.ServiceProvider.GetRequiredService(); _controller = new RaspDataController(_context); } - - /* - * Post RaspData - */ + [Fact] public async Task CreateRaspData() { - // Arrange var raspData = new RaspData { MacAddress = "79:1C:89:6B:EC:C7", @@ -37,11 +33,9 @@ public async Task CreateRaspData() Rssi = -90, 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); @@ -54,15 +48,9 @@ public async Task CreateRaspData() [Fact] public async Task GetRaspData_ByName() { - - _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); @@ -74,16 +62,10 @@ public async Task GetRaspData_ByName() [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 + Assert.IsType(result); - - // Assert var deletedRaspData = await _context.RaspData.FindAsync(3); Assert.Null(deletedRaspData); // Ensure the venue is no longer in the database } @@ -94,11 +76,8 @@ public async Task DeleteRaspData_ById() [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..744b2e8 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs @@ -16,7 +16,6 @@ public class RaspberryPiControllerTests : IClassFixture(); _controller = new RaspberryPiController(_context); diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs index a209992..a6551e6 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs @@ -16,11 +16,9 @@ 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); - } /* @@ -30,17 +28,14 @@ public VenueControllerTests(CustomWebApplicationFactory factory) [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 @@ -54,14 +49,8 @@ public async Task CreateVenue() [Fact] public async Task GetVenue_ByName() { - _context.Venue.Add(new Venue { VenueID = 2, VenueName = "Test Venue" }); - await _context.SaveChangesAsync(); - - - // Act - var result = await _controller.GetVenue(2); - - // Assert + var result = await _controller.GetVenue(4); + var actionResult = Assert.IsType>(result); var venue = Assert.IsType(actionResult.Value); Assert.Equal("Test Venue", venue.VenueName); @@ -73,29 +62,21 @@ public async Task GetVenue_ByName() [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 - - // Assert: Verify the result is NoContent (204) + + var result = await _controller.DeleteVenue(98); + 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); } } From fada367452156e48b51c57e0ffe5f212187d3fd0 Mon Sep 17 00:00:00 2001 From: davidfraenkel Date: Sat, 10 May 2025 09:46:34 +0200 Subject: [PATCH 15/16] Comments for tests --- .../CustomWebApplicationFactory.cs | 14 ++-- .../DetectedDevicesIntegrationTests.cs | 32 ++++++-- .../Controllers/RaspDataIntegrationTests.cs | 73 +++---------------- .../Controllers/VenueIntegrationTests.cs | 31 +++++++- .../Helpers/DetectedDeviceHelperTest.cs | 7 ++ .../DetectedDevicesControllerTests.cs | 11 ++- .../Controllers/RaspDataControllerTests.cs | 58 +++++++++------ .../Controllers/RaspberryPiControllerTests.cs | 10 ++- .../Controllers/VenueControllerTests.cs | 46 +++++++----- .../UnitTests/Services/CircleUtilsTests.cs | 54 ++++++++------ .../Services/HeatmapGeneratorTests.cs | 36 +++++---- 11 files changed, 213 insertions(+), 159 deletions(-) diff --git a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs index 61c3f25..539df3e 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs @@ -13,7 +13,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory { @@ -40,25 +40,25 @@ 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(); }); } diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/DetectedDevicesIntegrationTests.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/DetectedDevicesIntegrationTests.cs index 22e65a0..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 GetLatestValidHeatmap_returnBitstring() { var result = await _client.GetAsync($"api/DetectedDevices/getLatestValidHeatmap"); var responseContent = await result.Content.ReadAsStringAsync(); result.EnsureSuccessStatusCode(); - + Assert.False(string.IsNullOrWhiteSpace(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() { - + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Data", "raspOutputData.json"); var raspOutputData = JsonSerializer.Deserialize( @@ -48,14 +61,21 @@ public async Task HandleRaspPostRequest_test() var response = await _client.PostAsJsonAsync("/api/detectedDevices/uploadMultiple", raspOutputData); response.EnsureSuccessStatusCode(); _TestOutput.WriteLine($"response : {response}"); - + 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() { - + 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 59f2e55..bd16275 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/RaspDataIntegrationTests.cs @@ -21,77 +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() { var RaspData = new RaspData { MacAddress = "24:58:46:97:75:3F", RaspId = 3, Rssi = -82, UnixTimestamp = 1746530400000 }; - - var response = await _client.PostAsJsonAsync("/api/RaspData", RaspData); - response.EnsureSuccessStatusCode(); - - var returned = await response.Content.ReadFromJsonAsync(); - - 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}"); + var response = await _client.PostAsJsonAsync("/api/RaspData", RaspData); 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 returned = await response.Content.ReadFromJsonAsync(); - var getAfterDelete = await _client.GetAsync($"/api/Venue/{venue.VenueID}"); - Assert.Equal(HttpStatusCode.NotFound, getAfterDelete.StatusCode); + Assert.Equal("24:58:46:97:75:3F", returned.MacAddress); } - */ - } } diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs index 71aa0d9..117e3c3 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Controllers/VenueIntegrationTests.cs @@ -20,6 +20,13 @@ 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() { @@ -36,6 +43,13 @@ 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() { @@ -50,6 +64,14 @@ public async Task GetVenue__ReturnsVenue() 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() { @@ -69,12 +91,19 @@ public async Task GetVenue_UpdateVenue_ReturnsUpdatedVenue() // Assert - Get again and verify the updated name var confirmResponse = await _client.GetAsync($"/api/Venue/{id}"); - + var updatedVenue = await confirmResponse.Content.ReadFromJsonAsync(); 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() { diff --git a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs index 230d803..e6c6522 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/IntegrationTests/Helpers/DetectedDeviceHelperTest.cs @@ -30,6 +30,13 @@ 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() { diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs index aa3ad29..94e98e4 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/DetectedDevicesControllerTests.cs @@ -24,6 +24,13 @@ public DetectedDevicesControllerTests(CustomWebApplicationFactory factory) _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() { @@ -35,9 +42,9 @@ public async Task PostDetectedDevices_ReturnsOkResult() DeviceY = 4, Timestamp = 1745562072611 }; - + var result = await _controller.PostDetectedDevice(detectedDevice); - + 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 a540048..b95a80d 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspDataControllerTests.cs @@ -17,12 +17,17 @@ public class RaspDataControllerTests : IClassFixture(); _controller = new RaspDataController(_context); } - + + /// + /// Creating a RaspData + /// + /// + /// Expected to pass by creating a RaspData and checking Id And MacAddress + /// [Fact] public async Task CreateRaspData() { @@ -33,51 +38,60 @@ public async Task CreateRaspData() Rssi = -90, UnixTimestamp = 1746033900000 }; - + var result = await _controller.PostRaspData(raspData); - + 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() { - + var result = await _controller.GetRaspData(998); - + 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() { - - var result = await _controller.DeleteRaspData(999); // Pass the ID of the venue to delete - + + var result = await _controller.DeleteRaspData(999); Assert.IsType(result); - 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() { var result = await _controller.DeleteRaspData(999); - + Assert.IsType(result); } } diff --git a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs index 744b2e8..ff7b9b7 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/RaspberryPiControllerTests.cs @@ -21,9 +21,13 @@ public RaspberryPiControllerTests(CustomWebApplicationFactory factory) _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 a6551e6..93978be 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/UnitTests/Controllers/VenueControllerTests.cs @@ -21,10 +21,12 @@ public VenueControllerTests(CustomWebApplicationFactory factory) _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() { @@ -33,9 +35,9 @@ public async Task CreateVenue() VenueID = 1, VenueName = "Test Venue" }; - + var result = await _controller.PostVenue(venue); - + var createdAtActionResult = Assert.IsType(result.Result); // Assert it returns CreatedAtActionResult var returnedVenue = Assert.IsType(createdAtActionResult.Value); // Assert the value is a Venue object @@ -43,40 +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() { var result = await _controller.GetVenue(4); - + 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() { - + var result = await _controller.DeleteVenue(98); - + Assert.IsType(result); - + var deletedVenue = await _context.Venue.FindAsync(98); - Assert.Null(deletedVenue); + Assert.Null(deletedVenue); } [Fact] public async Task DeleteVenue_VenueNotFound_ReturnsNotFound() { - + 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() { From 02fb0d677857d670e90331abfa87acdf6faf1dbe Mon Sep 17 00:00:00 2001 From: davidfraenkel Date: Sat, 10 May 2025 09:49:13 +0200 Subject: [PATCH 16/16] Linting --- .../CrowdedBackend.Tests/CustomWebApplicationFactory.cs | 1 - CrowdedBackend/CrowdedBackend.sln.DotSettings.user | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs index 539df3e..4990411 100644 --- a/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs +++ b/CrowdedBackend/CrowdedBackend.Tests/CustomWebApplicationFactory.cs @@ -10,7 +10,6 @@ namespace CrowdedBackend.Tests; public class CustomWebApplicationFactory : WebApplicationFactory { - private long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); protected override void ConfigureWebHost(IWebHostBuilder builder) { 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