// The normative errors of spec ยง69: the message convention of the Go // reference, wrapping with a prefix and the code of an error, as // errors_test.go of datekeys-go and the errors tests of bytes.test.ts of // datekeys-ts. errors_spec_test.dart checks the catalogue against the spec. import 'package:datekeys/datekeys.dart'; import 'package:test/test.dart'; void main() { test('every code is written as the spec writes it, and printed so', () { for (final c in ErrorCode.values) { expect(c.code, matches(RegExp(r'^ERR_[A-Z_]+$'))); expect('$c', c.code); } expect(ErrorCode.values.map((c) => c.code).toSet(), hasLength(19)); }); test('a message is the context and the code, or the bare code', () { final e = DateKeysException( ErrorCode.integrity, 'capsule: truncated prelude', ); expect(e.message, 'capsule: truncated prelude: ERR_INTEGRITY'); expect('$e', 'capsule: truncated prelude: ERR_INTEGRITY'); expect(e.code, ErrorCode.integrity); expect(DateKeysException(ErrorCode.integrity).message, 'ERR_INTEGRITY'); expect( DateKeysException(ErrorCode.invalidMagic, '').message, 'ERR_INVALID_MAGIC', ); }); test('wrapping puts a prefix before the message and keeps the code', () { // fmt.Errorf("capsule: step 8: %w", fmt.Errorf("agewrap: %w", ErrRoundMismatch)) final err = DateKeysException( ErrorCode.roundMismatch, 'agewrap', ).wrap('capsule: step 8'); expect(err.message, 'capsule: step 8: agewrap: ERR_ROUND_MISMATCH'); expect(errorCode(err), 'ERR_ROUND_MISMATCH'); expect(err.code, isNot(ErrorCode.integrity)); final data = DateKeysException(ErrorCode.extensionDataInvalid) .wrap('capsule: step 4'); expect(data.message, 'capsule: step 4: ERR_EXTENSION_DATA_INVALID'); expect(data.code, ErrorCode.extensionDataInvalid); }); test('a failure kept as text adds no code', () { // The failure of a relay kept as text, as provider/drand.Client keeps // it: the error carries exactly one normative code. final relay = DateKeysException(ErrorCode.roundMismatch, 'relay'); final err = DateKeysException( ErrorCode.releaseUnavailable, 'drand: no relay returned a verified release for round 1000: ${relay.message}', ); expect(errorCode(err), 'ERR_RELEASE_UNAVAILABLE'); expect(err.message, contains('ERR_ROUND_MISMATCH')); expect( err.message, 'drand: no relay returned a verified release for round 1000: relay: ' 'ERR_ROUND_MISMATCH: ERR_RELEASE_UNAVAILABLE', ); }); test('errorCode is empty for anything but a DateKeys error', () { expect( errorCode(DateKeysException(ErrorCode.headInvalid)), 'ERR_HEAD_INVALID', ); expect(errorCode(const FormatException('plain')), ''); expect(errorCode(StateError('plain')), ''); expect(errorCode(null), ''); }); test('withContext prefixes DateKeys errors only, and keeps the stack', () { expect(withContext('ctx', () => 1), 1); StackTrace? inner; DateKeysException? caught; StackTrace? outer; void failing() { try { throw DateKeysException(ErrorCode.integrity, 'inner'); } on DateKeysException catch (_, stack) { inner = stack; rethrow; } } try { withContext('ctx', failing); } on DateKeysException catch (e, stack) { caught = e; outer = stack; } expect(caught?.message, 'ctx: inner: ERR_INTEGRITY'); expect(caught?.code, ErrorCode.integrity); expect(inner, isNotNull); expect('$outer', '$inner'); final plain = RangeError('plain'); expect(() => withContext('ctx', () => throw plain), throwsA(same(plain))); }); }