Skip to content

Commit cc8802c

Browse files
authored
test(server): add missing builder & middleware tests (#226)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a dedicated input mapping method for configuring middleware, enhancing API flexibility. - **Refactor** - Simplified middleware chaining by refining type handling and streamlining input parameters. - **Tests** - Expanded and reorganized test coverage for middleware functionality and route handling, ensuring robust error detection and improved reliability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent bf49833 commit cc8802c

File tree

5 files changed

+185
-86
lines changed

5 files changed

+185
-86
lines changed

packages/server/src/builder-variants.test-d.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,44 @@ describe('RouterBuilder', () => {
10461046
expectTypeOf<keyof typeof builder>().toEqualTypeOf<keyof typeof expected>()
10471047
})
10481048

1049+
it('.use', () => {
1050+
const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => {
1051+
expectTypeOf(input).toEqualTypeOf<unknown>()
1052+
expectTypeOf(context).toEqualTypeOf<CurrentContext>()
1053+
expectTypeOf(path).toEqualTypeOf<readonly string[]>()
1054+
expectTypeOf(procedure).toEqualTypeOf<
1055+
Procedure<Context, Context, AnySchema, AnySchema, ErrorMap, BaseMeta>
1056+
>()
1057+
expectTypeOf(output).toEqualTypeOf<MiddlewareOutputFn<unknown>>()
1058+
expectTypeOf(errors).toEqualTypeOf<ORPCErrorConstructorMap<typeof baseErrorMap>>()
1059+
expectTypeOf(signal).toEqualTypeOf<undefined | InstanceType<typeof AbortSignal>>()
1060+
1061+
return next({
1062+
context: {
1063+
extra: true,
1064+
},
1065+
})
1066+
})
1067+
1068+
expectTypeOf(applied).toEqualTypeOf<
1069+
RouterBuilder<
1070+
InitialContext,
1071+
CurrentContext & { extra: boolean },
1072+
typeof baseErrorMap,
1073+
BaseMeta
1074+
>
1075+
>()
1076+
1077+
// @ts-expect-error --- conflict context
1078+
builder.use(({ next }) => next({ context: { db: 123 } }))
1079+
// conflict but not detected
1080+
expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf<never>()
1081+
// @ts-expect-error --- input is not match
1082+
builder.use(({ next }, input: 'invalid') => next({}))
1083+
// @ts-expect-error --- output is not match
1084+
builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}))
1085+
})
1086+
10491087
it('.prefix', () => {
10501088
expectTypeOf(builder.prefix('/test')).toEqualTypeOf<
10511089
RouterBuilder<InitialContext, CurrentContext, typeof baseErrorMap, BaseMeta>

packages/server/src/builder.test.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,35 @@ describe('builder', () => {
143143
})
144144
})
145145

146-
it('.use', () => {
147-
const mid2 = vi.fn()
148-
const applied = builder.use(mid2)
146+
describe('.use', () => {
147+
it('without map input', () => {
148+
const mid2 = vi.fn()
149+
const applied = builder.use(mid2)
149150

150-
expect(applied).instanceOf(Builder)
151-
expect(applied).not.toBe(builder)
152-
expect(applied['~orpc']).toEqual({
153-
...def,
154-
middlewares: [mid, mid2],
151+
expect(applied).instanceOf(Builder)
152+
expect(applied).not.toBe(builder)
153+
expect(applied['~orpc']).toEqual({
154+
...def,
155+
middlewares: [mid, mid2],
156+
})
157+
})
158+
159+
it('with map input', () => {
160+
const mid2 = vi.fn()
161+
const map2 = vi.fn()
162+
163+
decorateMiddlewareSpy.mockImplementationOnce(mid => ({
164+
mapInput: (map: any) => [mid, map] as any,
165+
}) as any)
166+
167+
const applied = builder.use(mid2, map2)
168+
169+
expect(applied).instanceOf(Builder)
170+
expect(applied).not.toBe(builder)
171+
expect(applied['~orpc']).toEqual({
172+
...def,
173+
middlewares: [mid, [mid2, map2]],
174+
})
155175
})
156176
})
157177

packages/server/src/middleware-decorated.test-d.ts

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import type { AnySchema, ErrorMap } from '@orpc/contract'
12
import type { baseErrorMap, BaseMeta } from '../../contract/tests/shared'
23
import type { CurrentContext } from '../tests/shared'
4+
import type { Context, MergedContext } from './context'
35
import type { ORPCErrorConstructorMap } from './error'
4-
import type { Middleware } from './middleware'
6+
import type { Middleware, MiddlewareOutputFn } from './middleware'
57
import type { DecoratedMiddleware } from './middleware-decorated'
8+
import type { Procedure } from './procedure'
69

710
const decorated = {} as DecoratedMiddleware<
811
CurrentContext,
@@ -40,56 +43,92 @@ describe('DecoratedMiddleware', () => {
4043
BaseMeta
4144
>
4245
>()
46+
47+
// @ts-expect-error - invalid map input return
48+
decorated.mapInput((input: 'input') => ({ input: 123 }))
4349
})
4450

4551
describe('.concat', () => {
4652
it('without map input', () => {
47-
const mapped = decorated.concat(
48-
({ next }, input: { input2: string }) => next({ context: { extra2: true } }),
49-
)
53+
const applied = decorated.concat(({ context, next, path, procedure, errors, signal }, input, output) => {
54+
expectTypeOf(input).toEqualTypeOf<{ input: string }>()
55+
expectTypeOf(context).toEqualTypeOf<CurrentContext & { extra: boolean }>()
56+
expectTypeOf(path).toEqualTypeOf<readonly string[]>()
57+
expectTypeOf(procedure).toEqualTypeOf<
58+
Procedure<Context, Context, AnySchema, AnySchema, ErrorMap, BaseMeta>
59+
>()
60+
expectTypeOf(output).toEqualTypeOf<MiddlewareOutputFn<{ output: number }>>()
61+
expectTypeOf(errors).toEqualTypeOf<ORPCErrorConstructorMap<typeof baseErrorMap>>()
62+
expectTypeOf(signal).toEqualTypeOf<undefined | AbortSignal>()
63+
64+
return next({
65+
context: {
66+
extra2: true,
67+
},
68+
})
69+
})
5070

51-
expectTypeOf(mapped).toEqualTypeOf<
71+
expectTypeOf(applied).toEqualTypeOf<
5272
DecoratedMiddleware<
5373
CurrentContext,
54-
{ extra: boolean } & { extra2: boolean },
55-
{ input: string } & { input2: string },
74+
MergedContext<{ extra: boolean }, { extra2: boolean }>,
75+
{ input: string },
5676
{ output: number },
5777
ORPCErrorConstructorMap<typeof baseErrorMap>,
5878
BaseMeta
5979
>
6080
>()
6181

62-
decorated.concat(
63-
// @ts-expect-error - conflict context
64-
({ next }) => next({ context: { extra: 'invalid' } }),
65-
)
82+
// @ts-expect-error --- conflict context
83+
decorated.concat(({ next }) => next({ context: { extra: 'invalid' } }))
84+
// @ts-expect-error --- output is not match
85+
decorated.concat(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}))
6686
})
6787

6888
it('with map input', () => {
69-
const mapped = decorated.concat(
70-
({ next }, input) => {
71-
expectTypeOf(input).toEqualTypeOf<{ input: { input2: string } }>()
72-
return next({ context: { extra2: true } })
73-
},
74-
(input: { input2: string }) => ({ input }),
75-
)
89+
const applied = decorated.concat(({ context, next, path, procedure, errors, signal }, input: { mapped: boolean }, output) => {
90+
expectTypeOf(input).toEqualTypeOf<{ mapped: boolean }>()
91+
expectTypeOf(context).toEqualTypeOf<CurrentContext & { extra: boolean }>()
92+
expectTypeOf(path).toEqualTypeOf<readonly string[]>()
93+
expectTypeOf(procedure).toEqualTypeOf<
94+
Procedure<Context, Context, AnySchema, AnySchema, ErrorMap, BaseMeta>
95+
>()
96+
expectTypeOf(output).toEqualTypeOf<MiddlewareOutputFn<{ output: number }>>()
97+
expectTypeOf(errors).toEqualTypeOf<ORPCErrorConstructorMap<typeof baseErrorMap>>()
98+
expectTypeOf(signal).toEqualTypeOf<undefined | AbortSignal>()
99+
100+
return next({
101+
context: {
102+
extra2: true,
103+
},
104+
})
105+
}, (input) => {
106+
expectTypeOf(input).toEqualTypeOf<{ input: string }>()
76107

77-
expectTypeOf(mapped).toEqualTypeOf<
108+
return { mapped: true }
109+
})
110+
111+
expectTypeOf(applied).toEqualTypeOf<
78112
DecoratedMiddleware<
79113
CurrentContext,
80-
{ extra: boolean } & { extra2: boolean },
81-
{ input: string } & { input2: string },
114+
MergedContext<{ extra: boolean }, { extra2: boolean }>,
115+
{ input: string },
82116
{ output: number },
83117
ORPCErrorConstructorMap<typeof baseErrorMap>,
84118
BaseMeta
85119
>
86120
>()
87121

88122
decorated.concat(
89-
// @ts-expect-error - conflict context
90-
({ next }) => next({ context: { extra: 'invalid' } }),
91-
input => ({ mapped: input }),
123+
({ context, next, path, procedure, errors, signal }, input: { mapped: boolean }, output) => next(),
124+
// @ts-expect-error --- invalid map input
125+
input => ({ invalid: true }),
92126
)
127+
128+
// @ts-expect-error --- conflict context
129+
decorated.concat(({ next }) => next({ context: { extra: 'invalid' } }), input => ({ mapped: true }))
130+
// @ts-expect-error --- output is not match
131+
decorated.concat(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}), input => ({ mapped: true }))
93132
})
94133
})
95134
})
Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1+
import { baseErrorMap } from '../../contract/tests/shared'
12
import { decorateMiddleware } from './middleware-decorated'
23

4+
beforeEach(() => {
5+
vi.clearAllMocks()
6+
})
7+
38
describe('decorateMiddleware', () => {
4-
it('just a function', () => {
9+
it('just a copied function', () => {
510
const fn = vi.fn()
611
const decorated = decorateMiddleware(fn) as any
712

813
fn.mockReturnValueOnce('__mocked__')
914

15+
expect(decorated).not.toBe(fn)
1016
expect(decorated('input')).toBe('__mocked__')
1117
expect(fn).toHaveBeenCalledTimes(1)
1218
expect(fn).toHaveBeenCalledWith('input')
1319
})
1420

15-
it('can map input', () => {
16-
const fn = vi.fn()
21+
it('.mapInput', () => {
22+
const fn = Object.assign(vi.fn(), { '~orpcErrorMap': baseErrorMap })
1723
const map = vi.fn()
1824
const decorated = decorateMiddleware(fn).mapInput(map) as any
1925

@@ -29,64 +35,61 @@ describe('decorateMiddleware', () => {
2935
expect(fn).toHaveBeenCalledWith({}, '__input__')
3036
})
3137

32-
it('can concat', async () => {
38+
describe('.concat', () => {
3339
const fn = vi.fn()
3440
const fn2 = vi.fn()
3541
const next = vi.fn()
3642

37-
const decorated = decorateMiddleware((options, input, output) => {
43+
const mid1 = vi.fn((options, input, output) => {
3844
fn(options, input, output)
3945
return options.next({ context: { auth: 1, mid1: true } })
40-
}).concat((options, input, output) => {
46+
})
47+
48+
const mid2 = vi.fn((options, input, output) => {
4149
fn2(options, input, output)
4250
return options.next({ context: { auth: 2, mid2: true } })
43-
}) as any
51+
})
4452

45-
next.mockReturnValueOnce('__mocked__')
46-
const outputFn = vi.fn()
47-
const signal = AbortSignal.timeout(100)
48-
expect((await decorated({ next, context: { origin: true }, signal }, 'input', outputFn))).toBe('__mocked__')
53+
it('without map input', async () => {
54+
const decorated = decorateMiddleware(mid1).concat(mid2) as any
4955

50-
expect(fn).toHaveBeenCalledTimes(1)
51-
expect(fn).toHaveBeenCalledWith({ next: expect.any(Function), context: { origin: true }, signal }, 'input', outputFn)
56+
next.mockReturnValueOnce('__mocked__')
57+
const outputFn = vi.fn()
58+
const signal = AbortSignal.timeout(100)
59+
expect((await decorated({ next, context: { origin: true }, signal }, 'input', outputFn))).toBe('__mocked__')
5260

53-
expect(fn2).toHaveBeenCalledTimes(1)
54-
expect(fn2).toHaveBeenCalledWith({ next: expect.any(Function), context: { origin: true, auth: 1, mid1: true }, signal }, 'input', outputFn)
61+
expect(fn).toHaveBeenCalledTimes(1)
62+
expect(fn).toHaveBeenCalledWith({ next: expect.any(Function), context: { origin: true }, signal }, 'input', outputFn)
5563

56-
expect(next).toHaveBeenCalledTimes(1)
57-
expect(next).toHaveBeenCalledWith({ context: { auth: 2, mid2: true, mid1: true } })
58-
})
64+
expect(fn2).toHaveBeenCalledTimes(1)
65+
expect(fn2).toHaveBeenCalledWith({ next: expect.any(Function), context: { origin: true, auth: 1, mid1: true }, signal }, 'input', outputFn)
5966

60-
it('can concat with map input', async () => {
61-
const fn = vi.fn()
62-
const fn2 = vi.fn()
63-
const map = vi.fn()
64-
const next = vi.fn()
67+
expect(next).toHaveBeenCalledTimes(1)
68+
expect(next).toHaveBeenCalledWith({ context: { auth: 2, mid2: true, mid1: true } })
69+
})
6570

66-
const decorated = decorateMiddleware((options, input, output) => {
67-
fn(options, input, output)
68-
return options.next({ context: { auth: true } })
69-
}).concat((options, input, output) => {
70-
fn2(options, input, output)
71-
return options.next({})
72-
}, map) as any
71+
it('with map input', async () => {
72+
const map = vi.fn()
7373

74-
map.mockReturnValueOnce({ name: 'input' })
75-
next.mockReturnValueOnce('__mocked__')
74+
const decorated = decorateMiddleware(mid1).concat(mid2, map) as any
7675

77-
const outputFn = vi.fn()
78-
expect((await decorated({ next }, 'input', outputFn))).toBe('__mocked__')
76+
map.mockReturnValueOnce({ name: 'input' })
77+
next.mockReturnValueOnce('__mocked__')
7978

80-
expect(fn).toHaveBeenCalledTimes(1)
81-
expect(fn).toHaveBeenCalledWith({ next: expect.any(Function) }, 'input', outputFn)
79+
const outputFn = vi.fn()
80+
expect((await decorated({ next }, 'input', outputFn))).toBe('__mocked__')
8281

83-
expect(map).toHaveBeenCalledTimes(1)
84-
expect(map).toHaveBeenCalledWith('input')
82+
expect(fn).toHaveBeenCalledTimes(1)
83+
expect(fn).toHaveBeenCalledWith({ next: expect.any(Function) }, 'input', outputFn)
84+
85+
expect(map).toHaveBeenCalledTimes(1)
86+
expect(map).toHaveBeenCalledWith('input')
8587

86-
expect(fn2).toHaveBeenCalledTimes(1)
87-
expect(fn2).toHaveBeenCalledWith({ context: { auth: true }, next: expect.any(Function) }, { name: 'input' }, outputFn)
88+
expect(fn2).toHaveBeenCalledTimes(1)
89+
expect(fn2).toHaveBeenCalledWith({ context: { auth: 1, mid1: true }, next: expect.any(Function) }, { name: 'input' }, outputFn)
8890

89-
expect(next).toHaveBeenCalledTimes(1)
90-
expect(next).toHaveBeenCalledWith({ context: { auth: true } })
91+
expect(next).toHaveBeenCalledTimes(1)
92+
expect(next).toHaveBeenCalledWith({ context: { auth: 2, mid1: true, mid2: true } })
93+
})
9194
})
9295
})

0 commit comments

Comments
 (0)