Skip to content

Commit

Permalink
Merge pull request #13797 from patrickacioli/fix-fastify-global-route…
Browse files Browse the repository at this point in the history
…-prefix

fix(fastify-adapter): middleware not executed when root path is excluded
  • Loading branch information
kamilmysliwiec authored Nov 25, 2024
2 parents 43cf572 + ba761c1 commit 59374c9
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
131 changes: 131 additions & 0 deletions integration/hello-world/e2e/middleware-fastify.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
import * as request from 'supertest';
import { FastifyRequest } from 'fastify';

describe('Middleware (FastifyAdapter)', () => {
let app: NestFastifyApplication;
Expand Down Expand Up @@ -398,4 +400,133 @@ describe('Middleware (FastifyAdapter)', () => {
await app.close();
});
});

describe('should have data attached in middleware', () => {
@Controller()
class DataController {
@Get('data')
async data(@Req() req: FastifyRequest['raw']) {
return {
success: true,
extras: req?.['raw']?.extras,
pong: req?.['raw']?.headers?.ping,
};
}
@Get('pong')
async pong(@Req() req: FastifyRequest['raw']) {
return { success: true, pong: req?.['raw']?.headers?.ping };
}

@Get('')
async rootPath(@Req() req: FastifyRequest['raw']) {
return { success: true, root: true };
}
}

@Module({
controllers: [DataController],
})
class DataModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => {
req.extras = { data: 'Data attached in middleware' };
req.headers['ping'] = 'pong';
next();
})
.forRoutes('*');
}
}

beforeEach(async () => {
app = (
await Test.createTestingModule({
imports: [DataModule],
}).compile()
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
});

it(`GET forRoutes('*') with global prefix`, async () => {
app.setGlobalPrefix('/api');
await app.init();
await app.getHttpAdapter().getInstance().ready();
return app
.inject({
method: 'GET',
url: '/api/pong',
})
.then(({ payload }) =>
expect(payload).to.be.eql(
JSON.stringify({
success: true,
pong: 'pong',
}),
),
);
});

it(`GET forRoutes('*') without prefix config`, async () => {
await app.init();
await app.getHttpAdapter().getInstance().ready();
return app
.inject({
method: 'GET',
url: '/pong',
})
.then(({ payload }) =>
expect(payload).to.be.eql(
JSON.stringify({
success: true,
pong: 'pong',
}),
),
);
});

it(`GET forRoutes('*') with global prefix and exclude patterns`, async () => {
app.setGlobalPrefix('/api', { exclude: ['/'] });
await app.init();
await app.getHttpAdapter().getInstance().ready();

await request(app.getHttpServer())
.get('/')
.expect(200, { success: true, root: true });
});

it(`GET forRoutes('*') with global prefix and global prefix options`, async () => {
app.setGlobalPrefix('/api', { exclude: ['/'] });
await app.init();
await app.getHttpAdapter().getInstance().ready();

await request(app.getHttpServer())
.get('/api/data')
.expect(200, {
success: true,
extras: { data: 'Data attached in middleware' },
pong: 'pong',
});
await request(app.getHttpServer())
.get('/')
.expect(200, { success: true, root: true });
});

it(`GET forRoutes('*') with global prefix that not starts with /`, async () => {
app.setGlobalPrefix('api');
await app.init();
await app.getHttpAdapter().getInstance().ready();

await request(app.getHttpServer())
.get('/api/data')
.expect(200, {
success: true,
extras: { data: 'Data attached in middleware' },
pong: 'pong',
});
await request(app.getHttpServer()).get('/').expect(404);
});

afterEach(async () => {
await app.close();
});
});
});
12 changes: 12 additions & 0 deletions packages/platform-fastify/adapters/fastify-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export class FastifyAdapter<
> = FastifyInstance<TServer, TRawRequest, TRawResponse>,
> extends AbstractHttpAdapter<TServer, TRequest, TReply> {
protected readonly instance: TInstance;
protected _pathPrefix?: string;

private _isParserRegistered: boolean;
private isMiddieRegistered: boolean;
Expand Down Expand Up @@ -563,6 +564,11 @@ export class FastifyAdapter<
this.registerJsonContentParser(rawBody);

this._isParserRegistered = true;
this._pathPrefix = prefix
? !prefix.startsWith('/')
? `/${prefix}`
: prefix
: undefined;
}

public useBodyParser(
Expand Down Expand Up @@ -619,6 +625,12 @@ export class FastifyAdapter<
// Fallback to "(.*)" to support plugins like GraphQL
normalizedPath = normalizedPath === '/(.*)' ? '(.*)' : normalizedPath;

// Normalize the path to support the prefix if it set in application
normalizedPath =
this._pathPrefix && !normalizedPath.startsWith(this._pathPrefix)
? `${this._pathPrefix}${normalizedPath}(.*)`
: normalizedPath;

let re = pathToRegexp(normalizedPath);
re = hasEndOfStringCharacter ? new RegExp(re.source + '$', re.flags) : re;

Expand Down

0 comments on commit 59374c9

Please sign in to comment.