Updater v1 --upgrade + roadmap as flat backlog
Updater: watch-together --upgrade checks updates/manifest.json, downloads the tarball, verifies sha256, applies over the live install (extract-temp + delete-first move-over; rename-safe on Linux while running). Zero Flutter imports, 7 tests incl. stub-server flows. Version stamped via --dart-define=APP_VERSION in package script. Warns (never silently jumps) on proto mismatch. Roadmap: phases retired as tracker (history in git log), now flat Built/Backlog/Epics/Deferred; Jellyfin next feature, transcode never; updater expansion stays an epic. Endpoint publishing (manifest + release tarball) is user-owned.
This commit is contained in:
1 parent
b2173e82c8
commit
d77b0e804d
9 files changed
+505
-103
No files matched your search
@@ -0,0 +1,173 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:watch_together/src/update/updater.dart';
|
||||
|
||||
void main() {
|
||||
test('version compare', () {
|
||||
expect(isNewerVersion('0.1.0', '0.1.1'), isTrue);
|
||||
expect(isNewerVersion('0.1.0', '0.2.0'), isTrue);
|
||||
expect(isNewerVersion('0.1.0', '1.0.0'), isTrue);
|
||||
expect(isNewerVersion('0.0.0-dev', '0.1.0'), isTrue);
|
||||
expect(isNewerVersion('0.1.0', '0.1.0'), isFalse);
|
||||
expect(isNewerVersion('0.2.0', '0.1.9'), isFalse);
|
||||
});
|
||||
|
||||
test('manifest rejects missing fields', () {
|
||||
expect(() => UpdateManifest.fromJson({}), throwsFormatException);
|
||||
expect(
|
||||
() => UpdateManifest.fromJson({
|
||||
'version': '0.2.0',
|
||||
'protocol': 4,
|
||||
'url': 'https://x/y.tar.gz',
|
||||
}),
|
||||
throwsFormatException);
|
||||
});
|
||||
|
||||
test('findInstallRoot honors bundle layout', () async {
|
||||
final tmp = await Directory.systemTemp.createTemp('wt-root-test');
|
||||
try {
|
||||
final root = Directory('${tmp.path}/root');
|
||||
await Directory('${root.path}/data').create(recursive: true);
|
||||
await Directory('${root.path}/lib').create(recursive: true);
|
||||
final exe = File('${root.path}/watch_together')..createSync();
|
||||
expect(findInstallRoot(exe.path)?.path, root.path);
|
||||
expect(findInstallRoot('${tmp.path}/stray-binary'), isNull);
|
||||
} finally {
|
||||
await tmp.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
group('upgrade flow', () {
|
||||
late HttpServer server;
|
||||
late Directory work;
|
||||
late List<int> tarball;
|
||||
late String tarSha;
|
||||
var tarHits = 0;
|
||||
|
||||
setUp(() async {
|
||||
tarHits = 0;
|
||||
work = await Directory.systemTemp.createTemp('wt-up-test');
|
||||
final archive = Archive();
|
||||
final v2 = utf8.encode('binary-v2');
|
||||
archive.addFile(ArchiveFile('watch_together', v2.length, v2));
|
||||
final cfg = utf8.encode('fresh-config');
|
||||
archive.addFile(ArchiveFile('data/fresh.cfg', cfg.length, cfg));
|
||||
tarball = GZipEncoder().encode(TarEncoder().encode(archive));
|
||||
tarSha = sha256.convert(tarball).toString();
|
||||
server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
server.listen((req) async {
|
||||
if (req.uri.path == '/manifest.json') {
|
||||
final manifest = {
|
||||
'version': '0.2.0',
|
||||
'protocol': 4,
|
||||
'url':
|
||||
'http://127.0.0.1:${server.port}/watch-together-0.2.0.tar.gz',
|
||||
'sha256': tarSha,
|
||||
'notes': 'test build',
|
||||
};
|
||||
req.response
|
||||
..statusCode = 200
|
||||
..write(jsonEncode(manifest));
|
||||
} else if (req.uri.path == '/watch-together-0.2.0.tar.gz') {
|
||||
tarHits++;
|
||||
req.response
|
||||
..statusCode = 200
|
||||
..add(tarball);
|
||||
} else {
|
||||
req.response.statusCode = 404;
|
||||
}
|
||||
await req.response.close();
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await server.close(force: true);
|
||||
if (await work.exists()) {
|
||||
await work.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
Future<Directory> fakeRoot() async {
|
||||
final root = Directory('${work.path}/root');
|
||||
await Directory('${root.path}/data').create(recursive: true);
|
||||
await Directory('${root.path}/lib').create(recursive: true);
|
||||
await File('${root.path}/watch_together')
|
||||
.writeAsString('binary-v1', flush: true);
|
||||
return root;
|
||||
}
|
||||
|
||||
String manifestUrl() =>
|
||||
'http://127.0.0.1:${server.port}/manifest.json';
|
||||
|
||||
test('downloads, verifies and applies', () async {
|
||||
final root = await fakeRoot();
|
||||
final rc = await runUpgrade(
|
||||
currentVersion: '0.1.0',
|
||||
manifestUrl: manifestUrl(),
|
||||
installRoot: root);
|
||||
expect(rc, 0);
|
||||
expect(tarHits, 1);
|
||||
expect(await File('${root.path}/watch_together').readAsString(),
|
||||
'binary-v2');
|
||||
expect(await File('${root.path}/data/fresh.cfg').readAsString(),
|
||||
'fresh-config');
|
||||
});
|
||||
|
||||
test('up to date downloads nothing', () async {
|
||||
final root = await fakeRoot();
|
||||
final rc = await runUpgrade(
|
||||
currentVersion: '0.2.0',
|
||||
manifestUrl: manifestUrl(),
|
||||
installRoot: root);
|
||||
expect(rc, 0);
|
||||
expect(tarHits, 0);
|
||||
expect(await File('${root.path}/watch_together').readAsString(),
|
||||
'binary-v1');
|
||||
});
|
||||
|
||||
test('bad hash aborts, install untouched', () async {
|
||||
await server.close(force: true);
|
||||
server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
server.listen((req) async {
|
||||
if (req.uri.path == '/manifest.json') {
|
||||
req.response
|
||||
..statusCode = 200
|
||||
..write(jsonEncode({
|
||||
'version': '0.2.0',
|
||||
'protocol': 4,
|
||||
'url':
|
||||
'http://127.0.0.1:${server.port}/watch-together-0.2.0.tar.gz',
|
||||
'sha256': '0' * 64,
|
||||
'notes': '',
|
||||
}));
|
||||
} else {
|
||||
tarHits++;
|
||||
req.response
|
||||
..statusCode = 200
|
||||
..add(tarball);
|
||||
}
|
||||
await req.response.close();
|
||||
});
|
||||
final root = await fakeRoot();
|
||||
final rc = await runUpgrade(
|
||||
currentVersion: '0.1.0',
|
||||
manifestUrl: manifestUrl(),
|
||||
installRoot: root);
|
||||
expect(rc, 1);
|
||||
expect(await File('${root.path}/watch_together').readAsString(),
|
||||
'binary-v1');
|
||||
});
|
||||
|
||||
test('unknown layout asks for manual update', () async {
|
||||
final rc = await runUpgrade(
|
||||
currentVersion: '0.1.0',
|
||||
manifestUrl: manifestUrl(),
|
||||
executablePath: '${work.path}/stray-binary');
|
||||
expect(rc, 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user