diff --git a/test/api/api_mode_config_test.dart b/test/api/api_mode_config_test.dart new file mode 100644 index 0000000..7f3496e --- /dev/null +++ b/test/api/api_mode_config_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:planb_logistic/providers/providers.dart'; + +void main() { + group('ApiMode', () { + test('has correct values', () { + expect(ApiMode.values.length, equals(2)); + expect(ApiMode.values, contains(ApiMode.http)); + expect(ApiMode.values, contains(ApiMode.grpc)); + }); + }); + + group('ApiModeConfig', () { + test('development config defaults to HTTP', () { + const config = ApiModeConfig.development; + + expect(config.mode, equals(ApiMode.http)); + expect(config.isHttp, isTrue); + expect(config.isGrpc, isFalse); + expect(config.fallbackToHttpOnError, isTrue); + }); + + test('developmentGrpc config uses gRPC', () { + const config = ApiModeConfig.developmentGrpc; + + expect(config.mode, equals(ApiMode.grpc)); + expect(config.isGrpc, isTrue); + expect(config.isHttp, isFalse); + expect(config.fallbackToHttpOnError, isTrue); + }); + + test('production config defaults to HTTP', () { + const config = ApiModeConfig.production; + + expect(config.mode, equals(ApiMode.http)); + expect(config.isHttp, isTrue); + expect(config.isGrpc, isFalse); + expect(config.fallbackToHttpOnError, isFalse); + }); + + test('productionGrpc config uses gRPC without fallback', () { + const config = ApiModeConfig.productionGrpc; + + expect(config.mode, equals(ApiMode.grpc)); + expect(config.isGrpc, isTrue); + expect(config.isHttp, isFalse); + expect(config.fallbackToHttpOnError, isFalse); + }); + + test('custom config can be created', () { + const config = ApiModeConfig( + mode: ApiMode.grpc, + fallbackToHttpOnError: false, + ); + + expect(config.mode, equals(ApiMode.grpc)); + expect(config.isGrpc, isTrue); + expect(config.fallbackToHttpOnError, isFalse); + }); + + test('default fallbackToHttpOnError is true', () { + const config = ApiModeConfig(mode: ApiMode.grpc); + + expect(config.fallbackToHttpOnError, isTrue); + }); + }); +} diff --git a/test/api/grpc_client_test.dart b/test/api/grpc_client_test.dart new file mode 100644 index 0000000..6794f4f --- /dev/null +++ b/test/api/grpc_client_test.dart @@ -0,0 +1,85 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:planb_logistic/api/grpc_client.dart'; +import 'package:planb_logistic/api/grpc_config.dart'; + +void main() { + group('GrpcCqrsApiClient', () { + test('can be created with development config', () { + final client = GrpcCqrsApiClient( + config: GrpcConfig.development, + authService: null, + ); + + expect(client.config, equals(GrpcConfig.development)); + expect(client.isConnected, isFalse); + }); + + test('can be created with production config', () { + final client = GrpcCqrsApiClient( + config: GrpcConfig.production, + authService: null, + ); + + expect(client.config, equals(GrpcConfig.production)); + expect(client.isConnected, isFalse); + }); + + test('channel is created lazily', () { + final client = GrpcCqrsApiClient( + config: GrpcConfig.development, + authService: null, + ); + + expect(client.isConnected, isFalse); + + // Access the channel to trigger lazy initialization + final channel = client.channel; + + expect(channel, isNotNull); + expect(client.isConnected, isTrue); + }); + + test('delivery client is created lazily', () { + final client = GrpcCqrsApiClient( + config: GrpcConfig.development, + authService: null, + ); + + // Access delivery client (implicitly creates channel first) + final deliveryClient = client.deliveryClient; + + expect(deliveryClient, isNotNull); + expect(client.isConnected, isTrue); + }); + + test('shutdown clears channel and client', () async { + final client = GrpcCqrsApiClient( + config: GrpcConfig.development, + authService: null, + ); + + // Initialize the channel + final _ = client.channel; + expect(client.isConnected, isTrue); + + // Shutdown + await client.shutdown(); + expect(client.isConnected, isFalse); + }); + + test('terminate clears channel and client', () async { + final client = GrpcCqrsApiClient( + config: GrpcConfig.development, + authService: null, + ); + + // Initialize the channel + final _ = client.channel; + expect(client.isConnected, isTrue); + + // Terminate + await client.terminate(); + expect(client.isConnected, isFalse); + }); + }); +} diff --git a/test/api/grpc_config_test.dart b/test/api/grpc_config_test.dart new file mode 100644 index 0000000..4094b0c --- /dev/null +++ b/test/api/grpc_config_test.dart @@ -0,0 +1,56 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:planb_logistic/api/grpc_config.dart'; + +void main() { + group('GrpcConfig', () { + test('development config has correct values', () { + const config = GrpcConfig.development; + + expect(config.host, equals('192.168.88.228')); + expect(config.port, equals(5011)); + expect(config.useTls, isFalse); + expect(config.allowSelfSignedCertificate, isTrue); + expect(config.timeout, equals(const Duration(seconds: 30))); + expect(config.address, equals('192.168.88.228:5011')); + }); + + test('production config has correct values', () { + const config = GrpcConfig.production; + + expect(config.host, equals('grpc-route.goutezplanb.com')); + expect(config.port, equals(443)); + expect(config.useTls, isTrue); + expect(config.allowSelfSignedCertificate, isFalse); + expect(config.timeout, equals(const Duration(seconds: 30))); + expect(config.address, equals('grpc-route.goutezplanb.com:443')); + }); + + test('custom config can be created', () { + const config = GrpcConfig( + host: 'custom.example.com', + port: 9000, + timeout: Duration(seconds: 60), + useTls: true, + allowSelfSignedCertificate: true, + ); + + expect(config.host, equals('custom.example.com')); + expect(config.port, equals(9000)); + expect(config.useTls, isTrue); + expect(config.allowSelfSignedCertificate, isTrue); + expect(config.timeout, equals(const Duration(seconds: 60))); + expect(config.address, equals('custom.example.com:9000')); + }); + + test('default values are correctly applied', () { + const config = GrpcConfig( + host: 'test.example.com', + port: 443, + ); + + expect(config.useTls, isTrue); + expect(config.allowSelfSignedCertificate, isFalse); + expect(config.timeout, equals(const Duration(seconds: 30))); + }); + }); +} diff --git a/test/e2e/GRPC_E2E_VERIFICATION.md b/test/e2e/GRPC_E2E_VERIFICATION.md new file mode 100644 index 0000000..64b4a5e --- /dev/null +++ b/test/e2e/GRPC_E2E_VERIFICATION.md @@ -0,0 +1,192 @@ +# gRPC Integration E2E Verification Guide + +This document provides steps for verifying the gRPC integration end-to-end. + +## Prerequisites + +1. gRPC backend server running at `192.168.88.228:5011` +2. Flutter environment configured +3. Valid test credentials for authentication + +## Step 1: Enable gRPC Mode + +To enable gRPC mode, override the `apiModeConfigProvider` in the app's `ProviderScope`: + +### Option A: Code Change (for testing) + +Edit `lib/main.dart` to add the provider override: + +```dart +import 'package:planb_logistic/providers/providers.dart'; + +void main() { + runApp( + ProviderScope( + overrides: [ + // Enable gRPC mode with fallback to HTTP + apiModeConfigProvider.overrideWithValue(ApiModeConfig.developmentGrpc), + ], + child: const MyApp(), + ), + ); +} +``` + +### Option B: Environment-Based Toggle + +The app can also be configured to check an environment variable or build flag: + +```dart +final useGrpc = const bool.fromEnvironment('USE_GRPC', defaultValue: false); +final apiMode = useGrpc ? ApiModeConfig.developmentGrpc : ApiModeConfig.development; +``` + +Then run with: +```bash +flutter run -d chrome --dart-define=USE_GRPC=true +``` + +## Step 2: Login to App + +1. Launch the app in Chrome browser: + ```bash + flutter run -d chrome + ``` + +2. Click "Login with Keycloak" button +3. Enter valid credentials +4. Wait for redirect back to the app + +**Expected:** User is authenticated and redirected to the main app. + +## Step 3: Navigate to Routes Page + +1. After login, navigate to the Routes page +2. The app should fetch delivery routes via gRPC + +**Expected Behavior:** +- Routes list should populate with delivery route data +- No console errors related to gRPC +- In browser DevTools console, you should see: + - No "gRPC failed" messages (unless backend is unavailable) + - If gRPC fails and fallback is enabled, you may see: "gRPC failed, falling back to HTTP: ..." + +**Verification Points:** +- Routes display with correct data (id, name, deliveries count) +- Loading indicator shows during fetch +- Error state handled gracefully if backend unavailable + +## Step 4: Select a Route - Verify Deliveries Load + +1. Click on a route from the list +2. Navigate to the deliveries page for that route + +**Expected Behavior:** +- Deliveries list should populate with delivery data for the selected route +- Each delivery should show: + - Delivery index/number + - Customer name + - Address information + - Status (delivered/pending/skipped) +- Warehouse delivery should appear at the end of the list + +**Verification Points:** +- Correct number of deliveries loaded +- All delivery fields properly mapped from gRPC response +- Warehouse delivery appended correctly + +## Step 5: Mark a Delivery Complete + +1. Select an uncompleted delivery +2. Mark it as complete (tap completion button) + +**Expected Behavior:** +- Command sent via gRPC to `completeDelivery` endpoint +- Delivery status updates to "completed" +- UI refreshes to show new status + +**Verification Points:** +- No error messages +- Delivery marked as completed in the list +- Timestamp recorded for completion + +## Step 6: Check Browser Console for gRPC Logs + +Open browser DevTools (F12) and check the Console tab for: + +### Success Indicators: +- No error messages related to gRPC +- Successful data loading without fallback messages + +### Warning Indicators (acceptable): +- "gRPC failed, falling back to HTTP: ..." - indicates gRPC failed but HTTP fallback worked + +### Error Indicators (need investigation): +- "UNAUTHENTICATED" errors - token issue +- "UNAVAILABLE" errors - backend not reachable +- "DEADLINE_EXCEEDED" errors - timeout +- Unhandled exceptions + +## Verification Checklist + +| Step | Check | Status | +|------|-------|--------| +| 1 | gRPC mode enabled via provider override | [ ] | +| 2 | Login successful | [ ] | +| 3 | Routes load (via gRPC or HTTP fallback) | [ ] | +| 4 | Deliveries load for selected route | [ ] | +| 5 | Complete delivery command works | [ ] | +| 6 | No critical console errors | [ ] | + +## Troubleshooting + +### gRPC Backend Unavailable + +If the gRPC backend at `192.168.88.228:5011` is not reachable: + +1. **With fallback enabled (default):** App falls back to HTTP automatically +2. **Without fallback:** App shows error state + +To test with HTTP only: +```dart +apiModeConfigProvider.overrideWithValue(ApiModeConfig.development) +``` + +### Authentication Issues + +If you see UNAUTHENTICATED errors: + +1. Check that the auth token is valid +2. Try logging out and back in +3. Verify the token is being sent in gRPC metadata + +### Connection Timeout + +If requests are timing out: + +1. Check network connectivity to `192.168.88.228:5011` +2. Verify the backend server is running +3. Check firewall/VPN settings + +## Production Configuration + +For production deployment, use: + +```dart +apiModeConfigProvider.overrideWithValue(ApiModeConfig.productionGrpc) +``` + +This connects to `grpc-route.goutezplanb.com:443` with TLS enabled. + +## Test Commands + +```bash +# Run unit tests (no backend required) +flutter test + +# Run with gRPC enabled +flutter run -d chrome --dart-define=USE_GRPC=true + +# Check gRPC backend is reachable (requires grpcurl) +grpcurl -plaintext 192.168.88.228:5011 list +```