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.
dateKeys-dart/test/formats_datekey_test.dart

166 lines
5.7 KiB

// DateKeys, instants and rounds (lib/src/datekey.dart), as datekey.test.ts
// of datekeys-ts: properties that hold for every value, on random values of
// a fixed seed: dk1_ strings that read back, instants that format and parse
// back to the nanosecond, and rounds whose time is the first at or after
// the instant. The values and the texts of Go are in the differential
// (formats_datekey.json and formats_time.json). It reads no file: it runs on
// the VM and compiled to JavaScript.
library;
import 'dart:math';
import 'package:datekeys/src/datekey.dart';
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/profile.dart';
import 'package:test/test.dart';
String codeOf(void Function() body) {
try {
body();
return 'ok';
} on DateKeysException catch (e) {
return e.code.code;
}
}
// A random int in 0..n-1 for n up to 2^53, from two halves.
int below(Random r, int n) {
final hi = r.nextInt(1 << 21);
final lo = r.nextInt(4294967296);
return (hi * 4294967296 + lo) % n;
}
void main() {
final q = quicknet();
test('dk1_ strings of valid DateKeys read back, and invalid ones have '
'none', () {
final r = Random(7);
for (var i = 0; i < 300; i++) {
final d = DateKey(
['datekeys:quicknet:v1', 'a', '0:._-', 'z' * 128][i % 4],
1 + below(r, maxDateKeyRound),
);
final s = compactDateKey(d);
expect(s, startsWith(dk1Prefix));
expect(parseDateKey(s), d);
expect('$d', s);
expect(
canonicalJson(d),
'{"version":1,"network":"${d.profileId}","round":${d.round}}',
);
}
for (final d in [
const DateKey('A', 1),
const DateKey('a', 0),
const DateKey('a', maxDateKeyRound + 1),
const DateKey('', 1),
]) {
expect([canonicalJson(d), compactDateKey(d)], [null, '']);
}
});
test('instants order by seconds, then nanoseconds', () {
Instant t(int s, int ns) => Instant(s, ns);
expect(compareInstants(t(1, 0), t(1, 0)), 0);
expect(compareInstants(t(1, 0), t(1, 1)), -1);
expect(compareInstants(t(1, 999999999), t(2, 0)), -1);
expect(compareInstants(t(2, 0), t(1, 999999999)), 1);
expect(compareInstants(t(-5, 3), t(-5, 2)), 1);
expect(t(3, 4), t(3, 4));
expect(t(3, 4).hashCode, t(3, 4).hashCode);
expect([t(2, 0), t(1, 5), t(1, 4)]..sort(), [t(1, 4), t(1, 5), t(2, 0)]);
expect(() => Instant(0, -1), throwsRangeError);
expect(() => Instant(0, 1000000000), throwsRangeError);
});
test('instants format and parse back, to the nanosecond', () {
final r = Random(11);
// From 0000-01-01 to 9999-12-31, in UTC.
const first = -62167219200;
const span = 253402300799 - first + 1;
for (var i = 0; i < 2000; i++) {
final t = Instant(
first + below(r, span),
[0, 1, 999999999, r.nextInt(1000000000)][i % 4],
);
expect(parseRfc3339(formatRfc3339Nano(t)), t);
final whole = parseRfc3339(formatRfc3339(t));
expect([whole.seconds, whole.nanos], [t.seconds, 0]);
expect('$t', formatRfc3339Nano(t));
}
expect(() => parseRfc3339('2023-08-23'), throwsFormatException);
});
test('instants convert to and from DateTime', () {
final t = DateTime.utc(2030, 1, 2, 3, 4, 5, 678);
expect(
Instant.fromDateTime(t),
Instant(t.millisecondsSinceEpoch ~/ 1000, 678000000),
);
expect(Instant.fromDateTime(t).toDateTime(), t);
final before = DateTime.utc(1969, 12, 31, 23, 59, 59, 500);
expect(Instant.fromDateTime(before), Instant(-1, 500000000));
expect(Instant(-1, 500000000).toDateTime(), before);
});
test('a long horizon is more than 365 days ahead, to the nanosecond', () {
final now = Instant(1790000000, 500);
Instant at(int s, int ns) => Instant(now.seconds + s, ns);
expect(longHorizonSeconds, 365 * 24 * 60 * 60);
expect(isLongHorizon(at(longHorizonSeconds, 500), now), isFalse);
expect(isLongHorizon(at(longHorizonSeconds, 501), now), isTrue);
expect(isLongHorizon(at(1, 0), now), isFalse);
});
test('the round of an instant is the first whose time is at or after it, '
'on profiles of other periods too', () {
final r = Random(13);
for (final p in [
q,
q.copyWith(period: 1),
q.copyWith(period: 7, genesisTime: 1),
q.copyWith(period: 86400, genesisTime: 253402300799 - 86400 * 10),
]) {
final last = p.maxRound;
final lastTime = roundTime(p, last).seconds;
expect(lastTime <= maxUnixTime, isTrue);
expect(lastTime + p.period > maxUnixTime, isTrue);
expect(codeOf(() => roundTime(p, last + 1)), 'ERR_DATEKEY_INVALID');
expect(codeOf(() => roundTime(p, 0)), 'ERR_DATEKEY_INVALID');
for (var i = 0; i < 300; i++) {
final at = Instant(
p.genesisTime + below(r, lastTime - p.genesisTime + 1),
[0, 0, 1, r.nextInt(1000000000)][i % 4],
);
final d = resolveDateKey(p, at);
final t = roundTime(p, d.round);
expect(compareInstants(t, at) >= 0, isTrue);
if (d.round > 1) {
expect(compareInstants(roundTime(p, d.round - 1), at) < 0, isTrue);
}
expect(unlockAt(d, p), t);
validateDateKey(d, p);
}
// Before the genesis and after the last round.
expect(
codeOf(() => resolveDateKey(p, Instant(p.genesisTime - 1, 999999999))),
'ERR_DATEKEY_INVALID',
);
expect(
codeOf(() => resolveDateKey(p, Instant(lastTime, 1))),
'ERR_DATEKEY_INVALID',
);
}
expect(unlockAt(const DateKey('other', 2), q), isNull);
expect(
codeOf(() => validateDateKey(const DateKey('other', 2), q)),
'ERR_PROFILE_MISMATCH',
);
expect(
codeOf(() => resolveDateKey(q.copyWith(period: 0), Instant(0))),
'ERR_UNKNOWN_PROFILE',
);
});
}

Powered by TurnKey Linux.