halkyon/test/deploy/first_start_test.dart

107 lines
3.4 KiB
Dart
Raw Normal View History

import 'dart:io';
import 'package:test/test.dart';
/// halkyon.li's first start (deploy/host/first-start.md, ADR 0039) against what the
/// compose files run: the sidecars' users as their containers run, their folders
/// where the containers mount them, and a role and database for each app's server.
void main() {
final steps = File('deploy/host/first-start.md').readAsStringSync();
String compose(String name) => File('deploy/$name').readAsStringSync();
/// The users step 1 makes, by name: their IDs.
final users = {
for (final match in RegExp(
r'(\w+):(\d{5})',
).allMatches(RegExp('for each in (.+); do').firstMatch(steps)![1]!))
match[1]!: match[2]!,
};
test("makes each sidecar's user with the ID its container runs as", () {
const runs = {
'jellyfin': 'compose.jellyfin.yaml',
'languagetool': 'compose.languagetool.yaml',
'brouter': 'compose.train.yaml',
'maptiles': 'compose.maps.yaml',
'photon': 'compose.search.yaml',
};
expect(users.keys, unorderedEquals(runs.keys));
for (final MapEntry(key: name, value: file) in runs.entries) {
final id = users[name]!;
expect(compose(file), contains('user: "$id:$id"'), reason: name);
}
});
test("never gives a sidecar the runner's ID, nor one ID twice", () {
expect(users.values, isNot(contains('61000')));
expect(users.values.toSet(), hasLength(users.length));
});
test("makes each sidecar's folder its own, where its container mounts "
'it', () {
const folders = {
'languagetool': (r'$d/languagetool/fasttext', './languagetool/fasttext'),
'brouter': (r'$d/brouter/segments', './brouter/segments'),
'maptiles': (r'$d/maps/tiles', './maps/tiles'),
'photon': (r'$d/photon/data', './photon/data'),
};
final mounted = [
for (final file in [
'compose.languagetool.yaml',
'compose.train.yaml',
'compose.maps.yaml',
'compose.search.yaml',
])
compose(file),
].join('\n');
for (final MapEntry(key: owner, value: (made, mount)) in folders.entries) {
expect(
RegExp('sudo install -d -o $owner -g $owner .*${RegExp.escape(made)}')
.hasMatch(steps),
isTrue,
reason: made,
);
expect(mounted, contains(mount), reason: mount);
}
});
test("gives each app's server a role of its own, owning its database "
'alone, and lets no role connect to a database not its own', () {
for (final app in ['train', 'todo', 'meditate']) {
expect(
steps,
contains("CREATE ROLE halkyon_$app LOGIN PASSWORD :'$app';"),
reason: app,
);
expect(
steps,
contains('CREATE DATABASE halkyon_$app OWNER halkyon_$app;'),
reason: app,
);
expect(
compose('compose.$app.yaml'),
contains(
r'${'
'${app.toUpperCase()}_POSTGRES_PASSWORD',
),
reason: app,
);
}
for (final app in ['learn', 'train', 'todo', 'meditate']) {
expect(
steps,
contains('REVOKE CONNECT ON DATABASE halkyon_$app FROM PUBLIC;'),
reason: app,
);
}
expect(steps, isNot(contains('SUPERUSER')));
expect(steps, isNot(contains('CREATEDB')));
});
test("pins the developer's key on deploy to redeploy.sh alone", () {
expect(
steps,
contains('restrict,command="/home/deploy/halkyon/deploy/redeploy.sh" %s'),
);
});
}