|
| 1 | +import { ORPCError } from '@orpc/server' |
| 2 | +import { Cause, Effect, Exit } from 'effect' |
| 3 | +import { catchORPCError, catchORPCErrorCode, catchORPCErrorCodes } from './error' |
| 4 | + |
| 5 | +describe('catchORPCError', () => { |
| 6 | + it('catches any ORPCError failure (data-last)', async () => { |
| 7 | + const handled = Effect.fail(new ORPCError('NOT_FOUND', { data: 'data' })).pipe( |
| 8 | + catchORPCError(error => Effect.succeed(`caught:${error.code}`)), |
| 9 | + ) |
| 10 | + |
| 11 | + await expect(Effect.runPromise(handled)).resolves.toEqual('caught:NOT_FOUND') |
| 12 | + }) |
| 13 | + |
| 14 | + it('catches any ORPCError failure (data-first)', async () => { |
| 15 | + const handled = catchORPCError( |
| 16 | + Effect.fail(new ORPCError('CONFLICT')), |
| 17 | + error => Effect.succeed(`caught:${error.code}`), |
| 18 | + ) |
| 19 | + |
| 20 | + await expect(Effect.runPromise(handled)).resolves.toEqual('caught:CONFLICT') |
| 21 | + }) |
| 22 | + |
| 23 | + it('does not catch non-ORPCError failures', async () => { |
| 24 | + const error = new Error('__TEST__') |
| 25 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 26 | + |
| 27 | + const exit = await Effect.runPromiseExit(catchORPCError(Effect.fail(error), handler)) |
| 28 | + |
| 29 | + expect(exit).toEqual(Exit.fail(error)) |
| 30 | + expect(handler).not.toHaveBeenCalled() |
| 31 | + }) |
| 32 | + |
| 33 | + it('does not catch defects', async () => { |
| 34 | + const error = new ORPCError('NOT_FOUND') |
| 35 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 36 | + |
| 37 | + const exit = await Effect.runPromiseExit(catchORPCError(Effect.die(error), handler)) |
| 38 | + |
| 39 | + expect(exit).toEqual(Exit.failCause(Cause.die(error))) |
| 40 | + expect(handler).not.toHaveBeenCalled() |
| 41 | + }) |
| 42 | + |
| 43 | + it('does not touch successful effects', async () => { |
| 44 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 45 | + |
| 46 | + await expect(Effect.runPromise(catchORPCError(Effect.succeed('output'), handler))).resolves.toEqual('output') |
| 47 | + expect(handler).not.toHaveBeenCalled() |
| 48 | + }) |
| 49 | + |
| 50 | + it('can fail again inside the handler', async () => { |
| 51 | + const error = new Error('__TEST__') |
| 52 | + |
| 53 | + const exit = await Effect.runPromiseExit( |
| 54 | + Effect.fail(new ORPCError('NOT_FOUND')).pipe( |
| 55 | + catchORPCError(() => Effect.fail(error)), |
| 56 | + ), |
| 57 | + ) |
| 58 | + |
| 59 | + expect(exit).toEqual(Exit.fail(error)) |
| 60 | + }) |
| 61 | +}) |
| 62 | + |
| 63 | +describe('catchORPCErrorCode', () => { |
| 64 | + it('catches ORPCError failures with a matching code (data-last)', async () => { |
| 65 | + const handled = Effect.fail(new ORPCError('NOT_FOUND', { data: 'data' })).pipe( |
| 66 | + catchORPCErrorCode('NOT_FOUND', error => Effect.succeed(`caught:${error.data}`)), |
| 67 | + ) |
| 68 | + |
| 69 | + await expect(Effect.runPromise(handled)).resolves.toEqual('caught:data') |
| 70 | + }) |
| 71 | + |
| 72 | + it('catches ORPCError failures with a matching code (data-first)', async () => { |
| 73 | + const handled = catchORPCErrorCode( |
| 74 | + Effect.fail(new ORPCError('CONFLICT', { data: 'data' })), |
| 75 | + 'CONFLICT', |
| 76 | + error => Effect.succeed(`caught:${error.data}`), |
| 77 | + ) |
| 78 | + |
| 79 | + await expect(Effect.runPromise(handled)).resolves.toEqual('caught:data') |
| 80 | + }) |
| 81 | + |
| 82 | + it('does not catch ORPCError failures with a different code', async () => { |
| 83 | + const error = new ORPCError('CONFLICT') as ORPCError<'CONFLICT', undefined> | ORPCError<'NOT_FOUND', undefined> |
| 84 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 85 | + |
| 86 | + const exit = await Effect.runPromiseExit( |
| 87 | + catchORPCErrorCode(Effect.fail(error), 'NOT_FOUND', handler), |
| 88 | + ) |
| 89 | + |
| 90 | + expect(exit).toEqual(Exit.fail(error)) |
| 91 | + expect(handler).not.toHaveBeenCalled() |
| 92 | + }) |
| 93 | + |
| 94 | + it('does not catch non-ORPCError failures, even with a matching code property', async () => { |
| 95 | + const error = Object.assign(new Error('__TEST__'), { code: 'NOT_FOUND' }) as Error | ORPCError<'NOT_FOUND', undefined> |
| 96 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 97 | + |
| 98 | + const exit = await Effect.runPromiseExit( |
| 99 | + catchORPCErrorCode(Effect.fail(error), 'NOT_FOUND', handler), |
| 100 | + ) |
| 101 | + |
| 102 | + expect(exit).toEqual(Exit.fail(error)) |
| 103 | + expect(handler).not.toHaveBeenCalled() |
| 104 | + }) |
| 105 | + |
| 106 | + it('does not touch successful effects', async () => { |
| 107 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 108 | + const effect = Effect.succeed('output') as Effect.Effect<string, ORPCError<'NOT_FOUND', undefined>> |
| 109 | + |
| 110 | + await expect( |
| 111 | + Effect.runPromise(catchORPCErrorCode(effect, 'NOT_FOUND', handler)), |
| 112 | + ).resolves.toEqual('output') |
| 113 | + expect(handler).not.toHaveBeenCalled() |
| 114 | + }) |
| 115 | + |
| 116 | + it('can fail again inside the handler', async () => { |
| 117 | + const error = new Error('__TEST__') |
| 118 | + |
| 119 | + const exit = await Effect.runPromiseExit( |
| 120 | + Effect.fail(new ORPCError('NOT_FOUND')).pipe( |
| 121 | + catchORPCErrorCode('NOT_FOUND', () => Effect.fail(error)), |
| 122 | + ), |
| 123 | + ) |
| 124 | + |
| 125 | + expect(exit).toEqual(Exit.fail(error)) |
| 126 | + }) |
| 127 | +}) |
| 128 | + |
| 129 | +describe('catchORPCErrorCodes', () => { |
| 130 | + it('catches ORPCError failures with the matching handler (data-last)', async () => { |
| 131 | + const conflictHandler = vi.fn(() => Effect.succeed('caught:conflict')) |
| 132 | + const error = new ORPCError('NOT_FOUND', { data: 'data' }) as ORPCError<'NOT_FOUND', string> | ORPCError<'CONFLICT', undefined> |
| 133 | + |
| 134 | + const handled = Effect.fail(error).pipe( |
| 135 | + catchORPCErrorCodes({ |
| 136 | + NOT_FOUND: error => Effect.succeed(`caught:${error.data}`), |
| 137 | + CONFLICT: conflictHandler, |
| 138 | + }), |
| 139 | + ) |
| 140 | + |
| 141 | + await expect(Effect.runPromise(handled)).resolves.toEqual('caught:data') |
| 142 | + expect(conflictHandler).not.toHaveBeenCalled() |
| 143 | + }) |
| 144 | + |
| 145 | + it('catches ORPCError failures with the matching handler (data-first)', async () => { |
| 146 | + const handled = catchORPCErrorCodes( |
| 147 | + Effect.fail(new ORPCError('CONFLICT', { data: 'data' })), |
| 148 | + { CONFLICT: error => Effect.succeed(`caught:${error.data}`) }, |
| 149 | + ) |
| 150 | + |
| 151 | + await expect(Effect.runPromise(handled)).resolves.toEqual('caught:data') |
| 152 | + }) |
| 153 | + |
| 154 | + it('does not catch ORPCError failures without a matching handler', async () => { |
| 155 | + const error = new ORPCError('CONFLICT') as ORPCError<'CONFLICT', undefined> | ORPCError<'NOT_FOUND', undefined> |
| 156 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 157 | + |
| 158 | + const exit = await Effect.runPromiseExit( |
| 159 | + catchORPCErrorCodes(Effect.fail(error), { NOT_FOUND: handler }), |
| 160 | + ) |
| 161 | + |
| 162 | + expect(exit).toEqual(Exit.fail(error)) |
| 163 | + expect(handler).not.toHaveBeenCalled() |
| 164 | + }) |
| 165 | + |
| 166 | + it('does not catch ORPCError failures whose handler is undefined', async () => { |
| 167 | + const error = new ORPCError('NOT_FOUND') |
| 168 | + |
| 169 | + const exit = await Effect.runPromiseExit( |
| 170 | + catchORPCErrorCodes(Effect.fail(error), { NOT_FOUND: undefined }), |
| 171 | + ) |
| 172 | + |
| 173 | + expect(exit).toEqual(Exit.fail(error)) |
| 174 | + }) |
| 175 | + |
| 176 | + it('does not catch non-ORPCError failures, even with a matching code property', async () => { |
| 177 | + const error = Object.assign(new Error('__TEST__'), { code: 'NOT_FOUND' }) as Error | ORPCError<'NOT_FOUND', undefined> |
| 178 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 179 | + |
| 180 | + const exit = await Effect.runPromiseExit( |
| 181 | + catchORPCErrorCodes(Effect.fail(error), { NOT_FOUND: handler }), |
| 182 | + ) |
| 183 | + |
| 184 | + expect(exit).toEqual(Exit.fail(error)) |
| 185 | + expect(handler).not.toHaveBeenCalled() |
| 186 | + }) |
| 187 | + |
| 188 | + it('does not touch successful effects', async () => { |
| 189 | + const handler = vi.fn(() => Effect.succeed('caught')) |
| 190 | + const effect = Effect.succeed('output') as Effect.Effect<string, ORPCError<'NOT_FOUND', undefined>> |
| 191 | + |
| 192 | + await expect( |
| 193 | + Effect.runPromise(catchORPCErrorCodes(effect, { NOT_FOUND: handler })), |
| 194 | + ).resolves.toEqual('output') |
| 195 | + expect(handler).not.toHaveBeenCalled() |
| 196 | + }) |
| 197 | + |
| 198 | + it('can fail again inside a handler', async () => { |
| 199 | + const error = new Error('__TEST__') |
| 200 | + |
| 201 | + const exit = await Effect.runPromiseExit( |
| 202 | + Effect.fail(new ORPCError('NOT_FOUND')).pipe( |
| 203 | + catchORPCErrorCodes({ NOT_FOUND: () => Effect.fail(error) }), |
| 204 | + ), |
| 205 | + ) |
| 206 | + |
| 207 | + expect(exit).toEqual(Exit.fail(error)) |
| 208 | + }) |
| 209 | +}) |
0 commit comments