import 'dart:io'; import 'package:test/test.dart'; import '../../apps/learn/release.dart'; import '../../apps/todo/release.dart'; import '../../apps/train/release.dart'; import '../../tool/src/release/apps.dart'; import '../../tool/src/release/version.dart'; import 'fixtures.dart'; void main() { test("an app's release names its files and tags by its id", () { expect(testApp.tagPrefix, 'notes-v'); expect(testApp.pubspec, 'apps/notes/halkyon_notes_flutter/pubspec.yaml'); expect(testApp.instance, 'notes.halkyon.eu'); expect( testApp.apkFile(const Version(0, 19, 0)), 'halkyon-notes-0.19.0.apk', ); expect( testApp.snapFile(const Version(0, 19, 0)), 'halkyon-notes_0.19.0_amd64.snap', ); }); test('a configuration that would release something wrong is refused', () { expect(testApp.problems(), isEmpty); expect( testApp.copyWith(id: 'Notes App').problems(), contains('The id "Notes App" is not lower-case letters and digits.'), ); expect( testApp.copyWith(serverUrl: 'http://notes.halkyon.eu/').problems(), contains("The server URL http://notes.halkyon.eu/ isn't HTTPS."), ); expect( testApp.copyWith(serverUrl: 'https://notes.halkyon.eu').problems(), contains( "The server URL https://notes.halkyon.eu doesn't end with a slash, " 'as the app is built with it.', ), ); expect( testApp .copyWith(flutterPackage: 'apps/todo/halkyon_todo_flutter') .problems(), contains( "The Flutter package apps/todo/halkyon_todo_flutter isn't in " 'apps/notes.', ), ); expect( testApp.copyWith(applicationId: 'notes').problems(), contains("The application ID notes isn't one Android takes."), ); }); test("Learn's release is registered, sound, and names files that exist", () { expect(releasedApps['learn'], same(learnRelease)); expect(learnRelease.problems(), isEmpty); // The test runs in the repository's root, as `dart test` does. for (final path in [ learnRelease.pubspec, learnRelease.android.certificateFile, ...learnRelease.releaseFiles, ]) { expect( FileSystemEntity.typeSync(path), isNot(FileSystemEntityType.notFound), reason: path, ); } expect( File('${learnRelease.snap!.directory}/snapcraft.yaml').readAsStringSync(), contains('name: ${learnRelease.snap!.name}\n'), ); }); test("Train's release is registered, sound, names files that exist, and " 'has a key of its own', () { expect(releasedApps['train'], same(trainRelease)); expect(trainRelease.problems(), isEmpty); for (final path in [ trainRelease.pubspec, trainRelease.android.certificateFile, ...trainRelease.releaseFiles, ]) { expect( FileSystemEntity.typeSync(path), isNot(FileSystemEntityType.notFound), reason: path, ); } expect( trainRelease.android.certificateFile, isNot(learnRelease.android.certificateFile), ); expect( trainRelease.android.defaultKeystore, isNot(learnRelease.android.defaultKeystore), ); expect( File( '${trainRelease.fdroid!.listing}/metadata/' '${trainRelease.applicationId}.yml', ).readAsStringSync(), contains('NonFreeDep'), ); }); test("Todo's release is registered, sound, names files that exist, and " 'has a key of its own', () { expect(releasedApps['todo'], same(todoRelease)); expect(todoRelease.problems(), isEmpty); for (final path in [ todoRelease.pubspec, todoRelease.android.certificateFile, ...todoRelease.releaseFiles, ]) { expect( FileSystemEntity.typeSync(path), isNot(FileSystemEntityType.notFound), reason: path, ); } for (final other in [learnRelease, trainRelease]) { expect( todoRelease.android.certificateFile, isNot(other.android.certificateFile), ); expect( todoRelease.android.defaultKeystore, isNot(other.android.defaultKeystore), ); expect( todoRelease.android.keystoreVariable, isNot(other.android.keystoreVariable), ); } final listing = File( '${todoRelease.fdroid!.listing}/metadata/' '${todoRelease.applicationId}.yml', ).readAsStringSync(); expect(listing, contains('NonFreeNet')); // Task and Calendar & Agenda (ADR 0033, D24), each one the repository // lists. final categories = [ for (final match in RegExp(r'^ - (.+)$', multiLine: true).allMatches( RegExp(r'Categories:\n((?: - .+\n)+)').firstMatch(listing)![1]!, )) match[1]!, ]; expect(categories, ['Task', 'Calendar & Agenda']); final listed = File('${todoRelease.fdroid!.listing}/config/categories.yml') .readAsStringSync(); for (final category in categories) { expect(listed, contains('\n$category:\n')); } }); test("Learn's snap is the root's; Todo's and Train's are built in clones of " 'their own, outside the repository', () { expect(learnRelease.snap!.atRoot, isTrue); for (final (app, name) in [ (todoRelease, 'halkyon-todo'), (trainRelease, 'halkyon-train'), ]) { final snap = app.snap!; expect(snap.atRoot, isFalse, reason: app.id); expect( snap.projectPath('/home/someone'), '/home/someone/.cache/halkyon-release/snap/$name', ); expect(snap.storeChannel, 'edge', reason: app.id); } }); for (final app in [todoRelease, trainRelease]) { group("${app.name}'s snap, built in a clone of its own with its folder in " "place of the root's snap/", () { final snap = app.snap!; final recipe = File('${snap.directory}/snapcraft.yaml') .readAsStringSync(); test('names the snap and the part the release tool takes', () { expect(recipe, contains('name: ${snap.name}\n')); expect(recipe, contains('\n ${snap.part}:\n after: [flutter]\n')); expect(recipe, contains('adopt-info: ${snap.part} ')); }); test('builds the app for its instance', () { expect(recipe, contains('cd ${app.flutterPackage}\n')); expect(recipe, contains('--dart-define=SERVER_URL=${app.serverUrl}\n')); }); test('has the files it names, as they will stand in the clone', () { // In the clone, the folder is snap/, the root's snap/local/ beside // it. for (final match in RegExp( r'(?