22// for details. All rights reserved. Use of this source code is governed by a
33// BSD-style license that can be found in the LICENSE file.
44
5+ import 'dart:async' ;
6+ import 'dart:io' ;
7+ import 'dart:math' ;
8+
9+ import 'package:clock/clock.dart' ;
510import 'package:gcloud/service_scope.dart' as ss;
611import 'package:meta/meta.dart' ;
712import 'package:postgres/postgres.dart' ;
813import 'package:pub_dev/service/secret/backend.dart' ;
914import 'package:pub_dev/shared/env_config.dart' ;
1015
16+ final _random = Random .secure ();
17+
1118/// Sets the primary database service.
1219void registerPrimaryDatabase (PrimaryDatabase database) =>
1320 ss.register (#_primaryDatabase, database);
@@ -26,13 +33,38 @@ class PrimaryDatabase {
2633 /// the secret backend, connects to it and registers the primary database
2734 /// service in the current scope.
2835 static Future <void > tryRegisterInScope () async {
29- final connectionString =
36+ var connectionString =
3037 envConfig.pubPostgresUrl ??
3138 (await secretBackend.lookup (SecretKey .postgresConnectionString));
32- if (connectionString == null ) {
39+ if (connectionString == null && envConfig.isRunningInAppengine ) {
3340 // ignore for now, must throw once we have the environment setup ready
3441 return ;
3542 }
43+ // The scope-specific custom database. We are creating a custom database for
44+ // each test run, in order to provide full isolation, however, this must not
45+ // be used in Appengine.
46+ String ? customDb;
47+ if (connectionString == null ) {
48+ (connectionString, customDb) = await _startOrUseLocalPostgresInDocker ();
49+ }
50+ if (customDb == null && ! envConfig.isRunningInAppengine) {
51+ customDb = await _createCustomDatabase (connectionString);
52+ }
53+
54+ if (customDb != null ) {
55+ if (envConfig.isRunningInAppengine) {
56+ throw StateError ('Should not use custom database inside AppEngine.' );
57+ }
58+
59+ final originalUrl = connectionString;
60+ connectionString = Uri .parse (
61+ connectionString,
62+ ).replace (path: customDb).toString ();
63+ ss.registerScopeExitCallback (() async {
64+ await _dropCustomDatabase (originalUrl, customDb! );
65+ });
66+ }
67+
3668 final database = await _fromConnectionString (connectionString);
3769 registerPrimaryDatabase (database);
3870 ss.registerScopeExitCallback (database.close);
@@ -48,10 +80,64 @@ class PrimaryDatabase {
4880 }
4981
5082 @visibleForTesting
51- Future <void > verifyConnection () async {
52- final rs = await _pg.execute ('SELECT 1 ' );
83+ Future <String > verifyConnection () async {
84+ final rs = await _pg.execute ('SELECT current_database(); ' );
5385 if (rs.length != 1 ) {
5486 throw StateError ('Connection is not returning expected rows.' );
5587 }
88+ return rs.single.single as String ;
89+ }
90+ }
91+
92+ Future <(String , String ?)> _startOrUseLocalPostgresInDocker () async {
93+ // sanity check
94+ if (envConfig.isRunningInAppengine) {
95+ throw StateError ('Missing connection URL in Appengine environment.' );
96+ }
97+
98+ // the default connection URL for local server
99+ final url = Uri (
100+ scheme: 'postgresql' ,
101+ host: 'localhost' ,
102+ port: 55432 ,
103+ path: 'postgres' ,
104+ userInfo: 'postgres:postgres' ,
105+ queryParameters: {'sslmode' : 'disable' },
106+ ).toString ();
107+
108+ try {
109+ // try opening the connection
110+ final customDb = await _createCustomDatabase (url);
111+ return (url, customDb);
112+ } catch (_) {
113+ // on failure start the local server
114+ final pr = await Process .run ('tool/start-local-postgres.sh' , []);
115+ if (pr.exitCode != 0 ) {
116+ throw StateError (
117+ 'Unexpect exit code from tool/start-local-postgres.sh\n ${pr .stderr }' ,
118+ );
119+ }
56120 }
121+ return (url, null );
122+ }
123+
124+ int _customDbCount = 0 ;
125+
126+ Future <String > _createCustomDatabase (String url) async {
127+ _customDbCount++ ;
128+ final dbName =
129+ 'fake_pub_${pid .toRadixString (36 )}'
130+ '${_customDbCount .toRadixString (36 )}'
131+ '${clock .now ().millisecondsSinceEpoch .toRadixString (36 )}'
132+ '${_random .nextInt (1 << 32 ).toRadixString (36 )}' ;
133+ final conn = await Connection .openFromUrl (url);
134+ await conn.execute ('CREATE DATABASE "$dbName ";' );
135+ await conn.close (force: true );
136+ return dbName;
137+ }
138+
139+ Future <void > _dropCustomDatabase (String url, String dbName) async {
140+ final conn = await Connection .openFromUrl (url);
141+ await conn.execute ('DROP DATABASE "$dbName ";' );
142+ await conn.close (force: true );
57143}
0 commit comments