Skip to content

Commit 0267e98

Browse files
add native tests
1 parent ce5d99a commit 0267e98

File tree

3 files changed

+128
-61
lines changed

3 files changed

+128
-61
lines changed
Lines changed: 106 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,126 @@
11
import 'dart:async';
2-
import 'dart:convert';
32

4-
import 'package:http/http.dart' as http;
3+
import 'package:powersync/powersync.dart';
54
import 'package:test/test.dart';
65

76
import 'server/sync_server/mock_sync_server.dart';
7+
import 'streaming_sync_test.dart';
8+
import 'utils/abstract_test_utils.dart';
9+
import 'utils/test_utils_impl.dart';
10+
11+
final testUtils = TestUtils();
812

913
void main() {
1014
late TestHttpServerHelper testServer;
15+
late String path;
1116

1217
setUp(() async {
18+
path = testUtils.dbPath();
1319
testServer = TestHttpServerHelper();
1420
await testServer.start();
1521
});
1622

1723
tearDown(() async {
24+
await testUtils.cleanDb(path: path);
1825
await testServer.stop();
1926
});
2027

21-
test('should receive events from the sync stream without waiting for close',
22-
() async {
23-
final client = http.Client();
24-
final request =
25-
http.Request('POST', testServer.uri.replace(path: '/sync/stream'));
26-
request.headers['Content-Type'] = 'application/json';
27-
28-
// Send the request and get the response stream
29-
final responseStream = await client.send(request);
30-
31-
final expectedEvents = ['event1', 'event2', 'event3'];
32-
final receivedEvents = <String>[];
33-
final completer = Completer<void>();
34-
35-
// Listen to the response stream for real-time processing of incoming events
36-
final subscription = responseStream.stream
37-
.transform(utf8.decoder)
38-
.transform(LineSplitter())
39-
.listen(
40-
(event) {
41-
receivedEvents.add(event);
42-
if (receivedEvents.length == expectedEvents.length) {
43-
completer.complete(); // Complete once all events are received
44-
}
45-
},
46-
onError: (e) => completer.completeError(e),
47-
);
48-
49-
// Programmatically trigger events on the server
50-
for (final event in expectedEvents) {
51-
testServer.addEvent('$event\n');
52-
await Future.delayed(
53-
Duration(milliseconds: 100)); // Small delay for each event
54-
}
55-
56-
// Wait for the events to be received
57-
await completer.future.timeout(Duration(seconds: 5));
58-
await subscription.cancel();
59-
client.close();
60-
61-
expect(receivedEvents.toSet().containsAll(expectedEvents.toSet()), isTrue);
28+
test('should connect to mock PowerSync instance', () async {
29+
final connector = TestConnector(() async {
30+
return PowerSyncCredentials(
31+
endpoint: testServer.uri.toString(),
32+
token: 'token not used here',
33+
expiresAt: DateTime.now());
34+
});
35+
36+
final db = PowerSyncDatabase.withFactory(
37+
await testUtils.testFactory(path: path),
38+
schema: defaultSchema,
39+
maxReaders: 3);
40+
await db.initialize();
41+
42+
final connectedCompleter = Completer();
43+
44+
db.statusStream.listen((status) {
45+
if (status.connected) {
46+
connectedCompleter.complete();
47+
}
48+
});
49+
50+
// Add a basic command for the test server to send
51+
testServer.addEvent('{"token_expires_in": 3600}\n');
52+
53+
await db.connect(connector: connector);
54+
await connectedCompleter.future;
55+
56+
expect(db.connected, isTrue);
57+
});
58+
59+
test('should trigger uploads when connection is established', () async {
60+
int uploadCounter = 0;
61+
Completer uploadTriggeredCompleter = Completer();
62+
63+
final connector = TestConnector(() async {
64+
return PowerSyncCredentials(
65+
endpoint: testServer.uri.toString(),
66+
token: 'token not used here',
67+
expiresAt: DateTime.now());
68+
}, uploadData: (database) async {
69+
uploadCounter++;
70+
uploadTriggeredCompleter.complete();
71+
throw Exception('No uploads occur here');
72+
});
73+
74+
final db = PowerSyncDatabase.withFactory(
75+
await testUtils.testFactory(path: path),
76+
schema: defaultSchema,
77+
maxReaders: 3);
78+
await db.initialize();
79+
80+
// Create an item which should trigger an upload.
81+
await db.execute(
82+
'INSERT INTO customers (id, name) VALUES (uuid(), ?)', ['steven']);
83+
84+
// Create a new completer to await the next upload
85+
uploadTriggeredCompleter = Completer();
86+
87+
// Connect the PowerSync instance
88+
final connectedCompleter = Completer();
89+
// The first connection attempt will fail
90+
final connectedErroredCompleter = Completer();
91+
92+
db.statusStream.listen((status) {
93+
// print('status updated: ${status.connected}, ${status.downloadError}');
94+
if (status.connected) {
95+
connectedCompleter.complete();
96+
}
97+
if (status.downloadError != null &&
98+
!connectedErroredCompleter.isCompleted) {
99+
connectedErroredCompleter.complete();
100+
}
101+
});
102+
103+
// The first command will not be valid, this simulates a failed connection
104+
testServer.addEvent('asdf\n');
105+
await db.connect(connector: connector);
106+
107+
// The connect operation should have triggered an upload (even though it fails to connect)
108+
await uploadTriggeredCompleter.future;
109+
expect(uploadCounter, equals(1));
110+
// Create a new completer for the next iteration
111+
uploadTriggeredCompleter = Completer();
112+
113+
// Connection attempt should initially fail
114+
await connectedErroredCompleter.future;
115+
expect(db.currentStatus.anyError, isNotNull);
116+
117+
// Now send a valid command. Which will result in successful connection
118+
await testServer.clearEvents();
119+
testServer.addEvent('{"token_expires_in": 3600}\n');
120+
await connectedCompleter.future;
121+
expect(db.connected, isTrue);
122+
123+
await uploadTriggeredCompleter.future;
124+
expect(uploadCounter, equals(2));
62125
});
63126
}

packages/powersync/test/server/sync_server/mock_sync_server.dart

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import 'package:shelf/shelf.dart';
66
import 'package:shelf/shelf_io.dart' as io;
77
import 'package:shelf_router/shelf_router.dart';
88

9+
// A basic Mock PowerSync service server which queues commands
10+
// which clients can receive via connecting to the `/sync/stream` route.
11+
// This assumes only one client will ever be connected at a time.
912
class TestHttpServerHelper {
10-
final StreamController<String> _controller = StreamController<String>();
13+
// Use a queued stream to make tests easier.
14+
StreamController<String> _controller = StreamController<String>();
1115
late HttpServer _server;
12-
// late Timer _timer;
13-
1416
Uri get uri => Uri.parse('http://localhost:${_server.port}');
1517

16-
// Start the HTTP server
1718
Future<void> start() async {
1819
final router = Router()
1920
..post('/sync/stream', (Request request) async {
@@ -30,27 +31,23 @@ class TestHttpServerHelper {
3031
});
3132
});
3233

33-
// Sending a newline gets the stream going and resolving on the client side
34-
_controller.add("\n");
35-
36-
// // Add data. The mock stream does not seem to resolve without data
37-
// _timer = Timer.periodic(Duration(seconds: 1), (Timer timer) {
38-
// // This code will execute every second
39-
// _controller.add('{ "token_expires_in": 3600}\n');
40-
// });
41-
4234
_server = await io.serve(router, 'localhost', 0);
4335
print('Test server running at ${_server.address}:${_server.port}');
4436
}
4537

46-
// Programmatically add data to the stream
38+
// Queue events which will be sent to connected clients.
4739
void addEvent(String data) {
4840
_controller.add(data);
4941
}
5042

51-
// Stop the HTTP server
43+
// Clear events. We rely on a buffered controller here. Create a new controller
44+
// in order to clear the buffer.
45+
Future<void> clearEvents() async {
46+
await _controller.close();
47+
_controller = StreamController<String>();
48+
}
49+
5250
Future<void> stop() async {
53-
// _timer.cancel();
5451
await _controller.close();
5552
await _server.close();
5653
}

packages/powersync/test/streaming_sync_test.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,23 @@ final testUtils = TestUtils();
1313

1414
class TestConnector extends PowerSyncBackendConnector {
1515
final Function _fetchCredentials;
16+
final Future<void> Function(PowerSyncDatabase)? _uploadData;
1617

17-
TestConnector(this._fetchCredentials);
18+
TestConnector(this._fetchCredentials,
19+
{Future<void> Function(PowerSyncDatabase)? uploadData})
20+
: _uploadData = uploadData;
1821

1922
@override
2023
Future<PowerSyncCredentials?> fetchCredentials() {
2124
return _fetchCredentials();
2225
}
2326

2427
@override
25-
Future<void> uploadData(PowerSyncDatabase database) async {}
28+
Future<void> uploadData(PowerSyncDatabase database) async {
29+
if (_uploadData != null) {
30+
await _uploadData(database);
31+
}
32+
}
2633
}
2734

2835
void main() {

0 commit comments

Comments
 (0)