From 58f7c037894b1aeffee96b71428ae1e9e08e1936 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Thu, 20 Jul 2023 18:59:31 +0300 Subject: [PATCH] refactor: Better tests for `Result` class (#23473) --- lib/util/result.spec.ts | 40 +++++++++++++++++++--------------------- lib/util/result.ts | 36 ++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/lib/util/result.spec.ts b/lib/util/result.spec.ts index 614485bd5c1dd0..7bc0271a4a507a 100644 --- a/lib/util/result.spec.ts +++ b/lib/util/result.spec.ts @@ -226,37 +226,37 @@ describe('util/result', () => { describe('Transforming', () => { it('transforms successful promise to value', async () => { - const res = await Result.wrap(Promise.resolve('foo')).transform((x) => + const res = await AsyncResult.ok('foo').transform((x) => x.toUpperCase() ); expect(res).toEqual(Result.ok('FOO')); }); it('transforms successful promise to Result', async () => { - const res = await Result.wrap(Promise.resolve('foo')).transform((x) => + const res = await AsyncResult.ok('foo').transform((x) => Result.ok(x.toUpperCase()) ); expect(res).toEqual(Result.ok('FOO')); }); it('skips transform for failed promises', async () => { - const res = Result.wrap(Promise.reject('oops')); + const res = AsyncResult.err('oops'); const fn = jest.fn((x: number) => x + 1); await expect(res.transform(fn)).resolves.toEqual(Result.err('oops')); expect(fn).not.toHaveBeenCalled(); }); it('asyncronously transforms successfull promise to value', async () => { - const res = await Result.wrap(Promise.resolve('foo')).transform((x) => + const res = await AsyncResult.ok('foo').transform((x) => Promise.resolve(x.toUpperCase()) ); expect(res).toEqual(Result.ok('FOO')); }); - it('asynchronously transforms successful Result promise to Result', async () => { - const res = await Result.wrap( - Promise.resolve(Result.ok('foo')) - ).transform((x) => Promise.resolve(Result.ok(x.toUpperCase()))); + it('asynchronously transforms successful AsyncResult to Result', async () => { + const res = await AsyncResult.ok('foo').transform((x) => + Promise.resolve(Result.ok(x.toUpperCase())) + ); expect(res).toEqual(Result.ok('FOO')); }); @@ -283,20 +283,20 @@ describe('util/result', () => { }); it('skips async transform for rejected promise', async () => { - const res: AsyncResult = Result.wrap( - Promise.reject('oops') - ); + const res: AsyncResult = AsyncResult.err('oops'); const fn = jest.fn((x: number) => Promise.resolve(x + 1)); await expect(res.transform(fn)).resolves.toEqual(Result.err('oops')); expect(fn).not.toHaveBeenCalled(); }); - it('skips async transform for error AsyncResult', async () => { + it('handles uncaught error from AsyncResult before transforming', async () => { const res: AsyncResult = new AsyncResult((_, reject) => reject('oops') ); const fn = jest.fn((x: number) => Promise.resolve(x + 1)); - await expect(res.transform(fn)).resolves.toEqual(Result.err('oops')); + await expect(res.transform(fn)).resolves.toEqual( + Result._uncaught('oops') + ); expect(fn).not.toHaveBeenCalled(); }); @@ -306,31 +306,31 @@ describe('util/result', () => { res.transform((_) => Promise.reject('oops')) ).resolves.toEqual(Result._uncaught('oops')); expect(logger.logger.warn).toHaveBeenCalledWith( - expect.anything(), + { err: 'oops' }, 'Result: unhandled async transform error' ); }); it('handles error thrown on promise transform', async () => { - const res = Result.wrap(Promise.resolve('foo')); + const res = AsyncResult.ok('foo'); await expect( res.transform(() => { throw 'bar'; }) ).resolves.toEqual(Result._uncaught('bar')); expect(logger.logger.warn).toHaveBeenCalledWith( - expect.anything(), + { err: 'bar' }, 'AsyncResult: unhandled transform error' ); }); it('handles error thrown on promise async transform', async () => { - const res = Result.wrap(Promise.resolve('foo')); + const res = AsyncResult.ok('foo'); await expect( res.transform(() => Promise.reject('bar')) ).resolves.toEqual(Result._uncaught('bar')); expect(logger.logger.warn).toHaveBeenCalledWith( - expect.anything(), + { err: 'bar' }, 'AsyncResult: unhandled async transform error' ); }); @@ -346,9 +346,7 @@ describe('util/result', () => { Result.ok(x.join('-')); type Res = Result; - const res: Res = await Result.wrap( - Promise.resolve('foo') - ) + const res: Res = await AsyncResult.ok('foo') .transform(fn1) .transform(fn2) .transform(fn3); diff --git a/lib/util/result.ts b/lib/util/result.ts index 4877ce8557b342..09768857835fa4 100644 --- a/lib/util/result.ts +++ b/lib/util/result.ts @@ -265,10 +265,10 @@ export class Result { fn: ( value: NonNullable ) => - | NonNullable - | Result - | Result + | Result + | Promise> | Promise> + | NonNullable ): Result | AsyncResult { if (!this.res.ok) { return Result.err(this.res.err); @@ -277,6 +277,10 @@ export class Result { try { const res = fn(this.res.val); + if (res instanceof Result) { + return res; + } + if (res instanceof Promise) { return new AsyncResult((resolve) => { res @@ -292,10 +296,6 @@ export class Result { }); } - if (res instanceof Result) { - return res; - } - return Result.ok(res); } catch (err) { logger.warn({ err }, 'Result: unhandled transform error'); @@ -320,6 +320,14 @@ export class AsyncResult extends Promise> { super(executor); } + static ok(val: NonNullable): AsyncResult { + return new AsyncResult((resolve) => resolve(Result.ok(val))); + } + + static err(err: NonNullable): AsyncResult { + return new AsyncResult((resolve) => resolve(Result.err(err))); + } + static wrap( promise: Promise> ): AsyncResult; @@ -429,7 +437,7 @@ export class AsyncResult extends Promise> { | Promise> | Promise> | NonNullable - ): AsyncResult { + ): AsyncResult { return new AsyncResult((resolve) => { this.then((oldResult) => { const { ok, val: value, err: error } = oldResult.unwrap(); @@ -440,6 +448,10 @@ export class AsyncResult extends Promise> { try { const newResult = fn(value); + if (newResult instanceof Result) { + return resolve(newResult); + } + if (newResult instanceof Promise) { return newResult .then((asyncRes) => @@ -456,18 +468,14 @@ export class AsyncResult extends Promise> { }); } - if (newResult instanceof Result) { - return resolve(newResult); - } - return resolve(Result.ok(newResult)); } catch (err) { logger.warn({ err }, 'AsyncResult: unhandled transform error'); return resolve(Result._uncaught(err)); } }).catch((err) => { - // Actually, this should never happen - resolve(Result.err(err)); + // Happens when `.unwrap()` of `oldResult` throws + resolve(Result._uncaught(err)); }); }); }