From 3e8e1eaa2bc21eed881be6e27da96a8bc0678736 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Tue, 15 Oct 2024 17:30:20 -0700 Subject: [PATCH] chore(runtime): Allow connecting to locally running libSQL servers --- packages/celest/CHANGELOG.md | 1 + .../celest/lib/src/runtime/data/connect.dart | 28 +++++--- .../test/runtime/data/connect_test.dart | 71 +++++++++++++++++++ 3 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 packages/celest/test/runtime/data/connect_test.dart diff --git a/packages/celest/CHANGELOG.md b/packages/celest/CHANGELOG.md index 38825e0..799c958 100644 --- a/packages/celest/CHANGELOG.md +++ b/packages/celest/CHANGELOG.md @@ -1,6 +1,7 @@ ## 1.0.1-wip - chore: Remove assumptions about where the project is deployed +- chore: Allow connecting to locally running libSQL servers ## 1.0.0 diff --git a/packages/celest/lib/src/runtime/data/connect.dart b/packages/celest/lib/src/runtime/data/connect.dart index 64298db..9293693 100644 --- a/packages/celest/lib/src/runtime/data/connect.dart +++ b/packages/celest/lib/src/runtime/data/connect.dart @@ -5,14 +5,15 @@ import 'package:celest/src/runtime/data/connect.io.dart' if (dart.library.js_interop) 'package:celest/src/runtime/data/connect.web.dart'; import 'package:drift/drift.dart'; import 'package:drift_hrana/drift_hrana.dart'; +import 'package:logging/logging.dart'; + +final Logger _logger = Logger('Celest.Data'); /// Checks the connection to the database by running a simple query. Future _checkConnection( Database db, ) async { - await db.transaction(() async { - await db.customSelect('SELECT 1').get(); - }); + await db.customSelect('SELECT 1').get(); return db; } @@ -31,16 +32,21 @@ Future connect( } final host = context.get(hostnameVariable); final token = context.get(tokenSecret); - if (host == null || token == null) { + if (host == null) { throw StateError( - 'Missing database hostname or token for $name. ' - 'Please set the `$hostnameVariable` and `$tokenSecret` values ' - 'in the environment or Celest configuration file.', + 'Missing database hostname $name. ' + 'Set the `$hostnameVariable` value in the environment or Celest ' + 'configuration file to connect.', + ); + } + final hostUri = Uri.tryParse(host) ?? Uri(scheme: 'libsql', host: host); + if (token == null) { + _logger.fine( + 'Missing database token for $name. Expecting a secret named ' + '`$tokenSecret` in the environment or Celest configuration file.', ); + _logger.fine('Connecting to $hostUri without a token.'); } - final connector = HranaDatabase( - Uri(scheme: 'libsql', host: host), - jwtToken: token, - ); + final connector = HranaDatabase(hostUri, jwtToken: token); return _checkConnection(factory(connector)); } diff --git a/packages/celest/test/runtime/data/connect_test.dart b/packages/celest/test/runtime/data/connect_test.dart new file mode 100644 index 0000000..bef9e97 --- /dev/null +++ b/packages/celest/test/runtime/data/connect_test.dart @@ -0,0 +1,71 @@ +@TestOn('vm') +@Tags(['e2e']) +library; + +import 'dart:io'; + +import 'package:celest/src/config/config_values.dart'; +import 'package:celest/src/core/context.dart'; +import 'package:celest/src/runtime/data/connect.dart'; +import 'package:drift/drift.dart'; +import 'package:drift_hrana/drift_hrana.dart'; +import 'package:platform/platform.dart'; +import 'package:test/fake.dart'; +import 'package:test/test.dart'; + +Future _findOpenPort() async { + final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); + final port = server.port; + await server.close(); + return port; +} + +Future _startLibSqlServer() async { + final port = await _findOpenPort(); + final process = await Process.start('turso', ['dev', '--port', '$port']); + addTearDown(process.kill); + return Uri.parse('ws://localhost:$port'); +} + +void main() { + group('connect', () { + test('allows local libSQL URIs', () async { + final host = await _startLibSqlServer(); + final platform = FakePlatform( + environment: { + 'CELEST_DATABASE_HOST': host.toString(), + }, + ); + Context.current.put(env.environment, 'production'); + Context.current.put(ContextKey.platform, platform); + await connect( + Context.current, + name: 'test', + factory: expectAsync1((executor) { + expect(executor, isA()); + return _FakeDatabase(); + }), + hostnameVariable: const env('CELEST_DATABASE_HOST'), + tokenSecret: const secret('CELEST_DATABASE_TOKEN'), + ); + }); + }); +} + +final class _FakeDatabase extends Fake implements GeneratedDatabase { + @override + Selectable customSelect( + String query, { + List> variables = const [], + Set> readsFrom = const {}, + }) { + return _FakeSelectable(); + } +} + +final class _FakeSelectable extends Fake implements Selectable { + @override + Future> get() { + return Future.value([]); + } +}