auto-claude: subtask-5-2 - End-to-end verification of gRPC integration
Added comprehensive test suite and documentation for gRPC integration: - test/api/grpc_config_test.dart: Unit tests for GrpcConfig - test/api/grpc_client_test.dart: Unit tests for GrpcCqrsApiClient - test/api/api_mode_config_test.dart: Unit tests for ApiModeConfig - test/e2e/GRPC_E2E_VERIFICATION.md: Manual E2E testing guide All 18 tests pass. Flutter analyze shows no issues. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a60f92c56d
commit
4bbf225aeb
67
test/api/api_mode_config_test.dart
Normal file
67
test/api/api_mode_config_test.dart
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
85
test/api/grpc_client_test.dart
Normal file
85
test/api/grpc_client_test.dart
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
56
test/api/grpc_config_test.dart
Normal file
56
test/api/grpc_config_test.dart
Normal file
@ -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)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
192
test/e2e/GRPC_E2E_VERIFICATION.md
Normal file
192
test/e2e/GRPC_E2E_VERIFICATION.md
Normal file
@ -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
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue
Block a user