feat: Add response delay generator to mock_server

This commit is contained in:
Anton Stubenbord
2023-05-29 12:19:34 +02:00
parent 886b82df9e
commit f46ae73f49
4 changed files with 102 additions and 39 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/navigation/push_routes.dart'; import 'package:paperless_mobile/core/navigation/push_routes.dart';
@@ -75,6 +76,17 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
}, },
).padded(), ).padded(),
], ],
bottom: PreferredSize(
preferredSize: Size.fromHeight(1),
child: BlocBuilder<DocumentSearchCubit, DocumentSearchState>(
builder: (context, state) {
if (state.isLoading) {
return const LinearProgressIndicator();
}
return const SizedBox.shrink();
},
),
),
), ),
body: Column( body: Column(
children: [ children: [
@@ -117,24 +129,17 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
childCount: historyMatches.length, childCount: historyMatches.length,
), ),
), ),
if (state.isLoading) SliverList(
const SliverToBoxAdapter( delegate: SliverChildBuilderDelegate(
child: Center( (context, index) => ListTile(
child: CircularProgressIndicator(), title: Text(suggestions[index]),
), leading: const Icon(Icons.search),
) onTap: () => _selectSuggestion(suggestions[index]),
else trailing: _buildInsertSuggestionButton(suggestions[index]),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
title: Text(suggestions[index]),
leading: const Icon(Icons.search),
onTap: () => _selectSuggestion(suggestions[index]),
trailing: _buildInsertSuggestionButton(suggestions[index]),
),
childCount: suggestions.length,
), ),
childCount: suggestions.length,
), ),
),
], ],
); );
} }

View File

@@ -75,7 +75,14 @@ Future<void> _initHive() async {
void main() async { void main() async {
if (kDebugMode) { if (kDebugMode) {
await LocalMockApiServer().start(); // URL: http://localhost:3131
// Login: admin:test
await LocalMockApiServer(
RandomDelayGenerator(
const Duration(milliseconds: 100),
const Duration(milliseconds: 800),
),
).start();
} }
await _initHive(); await _initHive();
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();

View File

@@ -1,10 +1,13 @@
library mock_server; library mock_server;
export 'response_delay_generator.dart';
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:mock_server/response_delay_generator.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
@@ -23,27 +26,29 @@ class LocalMockApiServer {
static get baseUrl => 'http://$host:$port/'; static get baseUrl => 'http://$host:$port/';
final DelayGenerator _delayGenerator;
late shelf_router.Router app; late shelf_router.Router app;
Future<Map<String, dynamic>> loadFixture(String name) async { Future<Map<String, dynamic>> loadFixture(String name) async {
var fixture = await rootBundle.loadString('packages/mock_server/fixtures/$name.json'); var fixture = await rootBundle.loadString('packages/mock_server/fixtures/$name.json');
return json.decode(fixture); return json.decode(fixture);
} }
LocalMockApiServer() { LocalMockApiServer([this._delayGenerator = const ZeroDelayGenerator()]) {
app = shelf_router.Router(); app = shelf_router.Router();
Map<String, dynamic> createdTags = {}; Map<String, dynamic> createdTags = {};
app.get('/api/', (Request req) async { app.get('/api/', (Request req) async {
log.info('Responding to /api'); log.info('Responding to /api');
return JsonMockResponse.ok({}); return JsonMockResponse.ok({}, _delayGenerator.nextDelay());
}); });
app.post('/api/token/', (Request req) async { app.post('/api/token/', (Request req) async {
log.info('Responding to /api/token/'); log.info('Responding to /api/token/');
var body = await req.bodyJsonMap(); var body = await req.bodyJsonMap();
if (body?['username'] == 'admin' && body?['password'] == 'test') { if (body?['username'] == 'admin' && body?['password'] == 'test') {
return JsonMockResponse.ok({'token': 'testToken'}); return JsonMockResponse.ok({'token': 'testToken'}, _delayGenerator.nextDelay());
} else { } else {
return Response.unauthorized('Unauthorized'); return Response.unauthorized('Unauthorized');
} }
@@ -52,37 +57,37 @@ class LocalMockApiServer {
app.get('/api/ui_settings/', (Request req) async { app.get('/api/ui_settings/', (Request req) async {
log.info('Responding to /api/ui_settings/'); log.info('Responding to /api/ui_settings/');
var data = await loadFixture('ui_settings'); var data = await loadFixture('ui_settings');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/users/<userId>/', (Request req, String userId) async { app.get('/api/users/<userId>/', (Request req, String userId) async {
log.info('Responding to /api/users/<userId>/'); log.info('Responding to /api/users/<userId>/');
var data = await loadFixture('user-1'); var data = await loadFixture('user-1');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/users/', (Request req, String userId) async { app.get('/api/users/', (Request req, String userId) async {
log.info('Responding to /api/users/'); log.info('Responding to /api/users/');
var data = await loadFixture('users'); var data = await loadFixture('users');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/groups/', (Request req, String userId) async { app.get('/api/groups/', (Request req, String userId) async {
log.info('Responding to /api/groups/'); log.info('Responding to /api/groups/');
var data = await loadFixture('groups'); var data = await loadFixture('groups');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/correspondents/', (Request req) async { app.get('/api/correspondents/', (Request req) async {
log.info('Responding to /api/correspondents/'); log.info('Responding to /api/correspondents/');
var data = await loadFixture('correspondents'); var data = await loadFixture('correspondents');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/document_types/', (Request req) async { app.get('/api/document_types/', (Request req) async {
log.info('Responding to /api/document_types/'); log.info('Responding to /api/document_types/');
var data = await loadFixture('doc_types'); var data = await loadFixture('doc_types');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/tags/', (Request req) async { app.get('/api/tags/', (Request req) async {
@@ -91,7 +96,7 @@ class LocalMockApiServer {
var data = await loadFixture("tags"); var data = await loadFixture("tags");
createdTags = data; createdTags = data;
} }
return JsonMockResponse.ok(createdTags); return JsonMockResponse.ok(createdTags, _delayGenerator.nextDelay());
}); });
app.post('/api/tags/', (Request req) async { app.post('/api/tags/', (Request req) async {
@@ -156,25 +161,25 @@ class LocalMockApiServer {
app.get('/api/storage_paths/', (Request req) async { app.get('/api/storage_paths/', (Request req) async {
log.info('Responding to /api/storage_paths/'); log.info('Responding to /api/storage_paths/');
var data = await loadFixture('storage_paths'); var data = await loadFixture('storage_paths');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/storage_paths/', (Request req) async { app.get('/api/storage_paths/', (Request req) async {
log.info('Responding to /api/storage_paths/'); log.info('Responding to /api/storage_paths/');
var data = await loadFixture('storage_paths'); var data = await loadFixture('storage_paths');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/saved_views/', (Request req) async { app.get('/api/saved_views/', (Request req) async {
log.info('Responding to /api/saved_views/'); log.info('Responding to /api/saved_views/');
var data = await loadFixture('saved_views'); var data = await loadFixture('saved_views');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/documents/', (Request req) async { app.get('/api/documents/', (Request req) async {
log.info('Responding to /api/documents/'); log.info('Responding to /api/documents/');
var data = await loadFixture('documents'); var data = await loadFixture('documents');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/documents/<docId>/thumb/', (Request req, String docId) async { app.get('/api/documents/<docId>/thumb/', (Request req, String docId) async {
@@ -194,39 +199,39 @@ class LocalMockApiServer {
app.get('/api/documents/<docId>/metadata/', (Request req, String docId) async { app.get('/api/documents/<docId>/metadata/', (Request req, String docId) async {
log.info('Responding to /api/documents/<docId>/metadata/'); log.info('Responding to /api/documents/<docId>/metadata/');
var data = await loadFixture('metadata'); var data = await loadFixture('metadata');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
//This is not yet used in the app //This is not yet used in the app
app.get('/api/documents/<docId>/suggestions/', (Request req, String docId) async { app.get('/api/documents/<docId>/suggestions/', (Request req, String docId) async {
log.info('Responding to /api/documents/<docId>/suggestions/'); log.info('Responding to /api/documents/<docId>/suggestions/');
var data = await loadFixture('suggestions'); var data = await loadFixture('suggestions');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
//This is not yet used in the app //This is not yet used in the app
app.get('/api/documents/<docId>/notes/', (Request req, String docId) async { app.get('/api/documents/<docId>/notes/', (Request req, String docId) async {
log.info('Responding to /api/documents/<docId>/notes/'); log.info('Responding to /api/documents/<docId>/notes/');
var data = await loadFixture('notes'); var data = await loadFixture('notes');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/tasks/', (Request req) async { app.get('/api/tasks/', (Request req) async {
log.info('Responding to /api/tasks/'); log.info('Responding to /api/tasks/');
var data = await loadFixture('tasks'); var data = await loadFixture('tasks');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/statistics/', (Request req) async { app.get('/api/statistics/', (Request req) async {
log.info('Responding to /api/statistics/'); log.info('Responding to /api/statistics/');
var data = await loadFixture('statistics'); var data = await loadFixture('statistics');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
app.get('/api/statistics/', (Request req) async { app.get('/api/statistics/', (Request req) async {
log.info('Responding to /api/statistics/'); log.info('Responding to /api/statistics/');
var data = await loadFixture('statistics'); var data = await loadFixture('statistics');
return JsonMockResponse.ok(data); return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
}); });
} }
@@ -264,8 +269,8 @@ extension on Request {
} }
extension JsonMockResponse on Response { extension JsonMockResponse on Response {
static ok<T>(T json, {int delay = 800}) async { static ok<T>(T json, Duration delay) async {
await Future.delayed(Duration(milliseconds: delay)); // Emulate lag await Future.delayed(delay); // Emulate lag
return Response.ok( return Response.ok(
jsonEncode(json), jsonEncode(json),

View File

@@ -0,0 +1,46 @@
import 'dart:math';
abstract interface class DelayGenerator {
Duration nextDelay();
}
class RandomDelayGenerator implements DelayGenerator {
/// Minimum allowed response delay
final Duration minDelay;
/// Maximum allowed response delay
final Duration maxDelay;
final Random _random = Random();
RandomDelayGenerator(this.minDelay, this.maxDelay);
@override
Duration nextDelay() {
return Duration(
milliseconds: minDelay.inMilliseconds +
_random.nextInt(
maxDelay.inMilliseconds - minDelay.inMilliseconds,
),
);
}
}
class ConstantDelayGenerator implements DelayGenerator {
final Duration delay;
const ConstantDelayGenerator(this.delay);
@override
Duration nextDelay() {
return delay;
}
}
class ZeroDelayGenerator implements DelayGenerator {
const ZeroDelayGenerator();
@override
Duration nextDelay() {
return Duration.zero;
}
}