You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
4.7 KiB
122 lines
4.7 KiB
// The opening against Go: every case of test/vectors/open_cases.json, which
|
|
// tool/open_go_vectors.go writes with capsule.Open of the reference, opened
|
|
// in memory and from a source read in pieces: the same result, text, checks
|
|
// with their details, release requests, state of the sink, content or files,
|
|
// and unusable extensions, and an output that is closed only when a capsule
|
|
// of format 1 or 2 opens and aborted after any failure (spec §56).
|
|
@TestOn('vm')
|
|
library;
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:datekeys/datekeys.dart';
|
|
import 'package:datekeys/src/sha256.dart' show sha256;
|
|
import 'package:test/test.dart';
|
|
|
|
import 'open_support.dart';
|
|
|
|
Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json;
|
|
|
|
final _fixtures = <String, Uint8List>{};
|
|
|
|
Uint8List fixture(String file) => _fixtures.putIfAbsent(
|
|
file,
|
|
() => File('testdata/fixtures/$file').readAsBytesSync(),
|
|
);
|
|
|
|
void main() {
|
|
final vectors = readJson('test/vectors/open_cases.json');
|
|
final fixtures = (vectors['fixtures']! as List).cast<Json>();
|
|
final steps = (vectors['steps']! as List).cast<Json>();
|
|
|
|
test('open_cases.json is of this spec, from the generator of this repo', () {
|
|
expect(vectors['spec'], specVersion);
|
|
expect(vectors['generator'], 'tool/open_go_vectors.go');
|
|
expect(fixtures, hasLength(greaterThanOrEqualTo(48)));
|
|
expect(steps, hasLength(greaterThan(100)));
|
|
// Every fixture opens with each of its credentials.
|
|
final names = {for (final c in fixtures) str(c, 'name').split(':').first};
|
|
final files = Directory('testdata/fixtures')
|
|
.listSync()
|
|
.map((f) => f.uri.pathSegments.last)
|
|
.where((n) => n.endsWith('.dkc'))
|
|
.map((n) => n.substring(0, n.length - 4))
|
|
.toSet();
|
|
expect(names, files);
|
|
expect(fixtures.every((c) => c['result'] == 'ok'), isTrue);
|
|
// The steps that the mutation corpus does not reach are here.
|
|
final failed = {
|
|
for (final c in steps)
|
|
if (c['result'] != 'ok') ((c['checks']! as List).last as List)[0],
|
|
};
|
|
expect(failed, containsAll(<int>[2, 3, 7, 8, 9, 10, 11, 12, 13, 14]));
|
|
expect(failed, containsAll(<int>[15, 17]));
|
|
});
|
|
|
|
test('every fixture opens to its plaintext, or to the files of its record, '
|
|
'with each credential', () async {
|
|
for (final c in fixtures) {
|
|
final name = str(c, 'name').split(':').first;
|
|
final r = readJson('testdata/fixtures/$name.json');
|
|
final plaintext = fixture(str(r, 'plaintext_file'));
|
|
expect(toHex(sha256(plaintext)), r['plaintext_sha256'], reason: name);
|
|
final o = await openCase(c, capsuleOf(c, fixture));
|
|
expect(o.opened.ok, isTrue, reason: str(c, 'name'));
|
|
if (r['format'] != 3) {
|
|
expect(o.output.received.toBytes(), plaintext, reason: str(c, 'name'));
|
|
continue;
|
|
}
|
|
// Format 3: each file is the range of BODY that the record names.
|
|
final at = r['content_offset']! as int;
|
|
final files = (r['files'] as List? ?? const []).cast<Json>();
|
|
expect(o.sink.files, hasLength(files.length), reason: str(c, 'name'));
|
|
for (var i = 0; i < files.length; i++) {
|
|
final f = files[i];
|
|
expect(
|
|
o.sink.files[i].toBytes(),
|
|
plaintext.sublist(at + (f['start']! as int), at + (f['end']! as int)),
|
|
reason: '${c['name']}: ${f['path']}',
|
|
);
|
|
}
|
|
expect(
|
|
[o.opened.head!.comment, o.opened.head!.author, o.opened.areaLen],
|
|
[r['comment'] ?? '', r['declared_author'] ?? '', r['area_len']],
|
|
reason: str(c, 'name'),
|
|
);
|
|
}
|
|
});
|
|
|
|
test('the comparison sees every field', () async {
|
|
final c = steps.firstWhere((c) => c['name'] == 'format 3: two files');
|
|
final o = await openCase(c, capsuleOf(c, fixture));
|
|
expect(differences(c, o), isEmpty);
|
|
for (final (key, value) in <(String, Object?)>[
|
|
('result', 'ERR_INTEGRITY'),
|
|
('text', 'ok.'),
|
|
('checks', (c['checks']! as List).sublist(1)),
|
|
('release_requests', 0),
|
|
('sink', 'aborted'),
|
|
('files', <Object?>[]),
|
|
('unusable', {'head': <Object?>[]}),
|
|
]) {
|
|
expect(differences({...c, key: value}, o), hasLength(1), reason: key);
|
|
}
|
|
});
|
|
|
|
for (final (section, cases) in [('fixtures', fixtures), ('steps', steps)]) {
|
|
group(section, () {
|
|
for (final c in cases) {
|
|
test(str(c, 'name'), () async {
|
|
final dkc = capsuleOf(c, fixture);
|
|
final memory = await openCase(c, dkc);
|
|
expect(differences(c, memory), isEmpty, reason: 'in memory');
|
|
final source = await openCase(c, dkc, fromSource: true);
|
|
expect(differences(c, source), isEmpty, reason: 'from a source');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|