Skip to content

Commit

Permalink
fix: updated the test and some fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Savio629 committed Aug 17, 2024
1 parent 37430b4 commit a5a72cf
Show file tree
Hide file tree
Showing 10 changed files with 15,108 additions and 3,281 deletions.
11,455 changes: 11,455 additions & 0 deletions packages/common/package-lock.json

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@yash-test/stencil",
"name": "@samagra-x/stencil",
"version": "0.0.6",
"description": "Stencil - An opinionated backend framework for NodeJS based on NestJS",
"author": "Yash Mittal (@techsavvyash) & Team SamagraX",
Expand Down Expand Up @@ -43,7 +43,7 @@
"@temporalio/client": "^1.8.6",
"@temporalio/worker": "^1.8.6",
"@types/multer": "^1.4.11",
"axios": "^1.7.2",
"axios": "^1.7.4",
"cache-manager": "^5.2.4",
"cache-manager-redis-store": "2",
"fastify": "^4.27.0",
Expand Down Expand Up @@ -84,7 +84,6 @@
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3"
},

"husky": {
"hooks": {
"pre-commit": "lint-staged"
Expand All @@ -97,4 +96,3 @@
]
}
}

30 changes: 15 additions & 15 deletions packages/common/src/interceptors/geoip.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { ConfigService } from '@nestjs/config';
import { Observable } from 'rxjs';
import { HttpService } from '@nestjs/axios';
import axios from 'axios';

@Injectable()
export class GeoIPInterceptor implements NestInterceptor {
Expand Down Expand Up @@ -39,54 +40,53 @@ export class GeoIPInterceptor implements NestInterceptor {
): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();

// Extract IP address
const clientIp =
request.headers['request.ip'] ||
request.headers['ip'] ||
request.ip ||
request.headers['x-forwarded-for'];

this.logger.verbose('Using IP address for geolocation:', clientIp);

try {
// Call the geolocation service to get the country from the IP
const { country, regionName } = await this.getLocation(clientIp);

if (
this.allowedCountries.length > 0 &&
!this.allowedCountries.includes(country)
) {
this.logger.error(
'Denying request from IP: ' + clientIp + ' country: ' + country,
);

throw new HttpException(
this.accessDeniedMessage,
this.accessDeniedStatus,
this.accessDeniedStatus
);
}

this.logger.log(
'Allowed request from IP: ' + clientIp + ' region: ' + regionName,
);
} catch (err) {
this.logger.error('Error occurred while reading the geoip database', err);
throw new InternalServerErrorException(
'Error occurred while reading the geoip database',
);
if (err instanceof HttpException) {
this.logger.error(
`HttpException: ${err.message} with status code: ${err.getStatus()}`
);
} else {
this.logger.error('Unexpected error: ', err.message);
throw new InternalServerErrorException(
'Error occurred while reading the geoip database',
);
}
}

// Continue handling the request
return next.handle();
}

async getLocation(ip: string): Promise<any> {
try {
const geoIp = this.configService.get<string>('GEO_IP');
return await this.httpService.axiosRef.get(
const response = await this.httpService.axiosRef.get(
`http://geoip.samagra.io/city/${ip}`,
);
return response.data;
} catch (err) {
this.logger.error('Error occurred while reading the geoip service', err);
throw new InternalServerErrorException(
'Error occurred while reading the geoip database',
);
Expand Down
142 changes: 77 additions & 65 deletions packages/common/src/interceptors/test/geoip.interceptor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,89 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GeoIPInterceptor } from '../geoip.interceptor';
import { HttpModule, HttpService } from '@nestjs/axios';

describe('Unit tests for geoIP interceptor', () => {
describe('Unit tests for geoIP interceptor', () => {
let interceptor: GeoIPInterceptor;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [GeoIPInterceptor],
}).compile();

interceptor = module.get<GeoIPInterceptor>(GeoIPInterceptor);
});

it('should be defined', () => {
expect(interceptor).toBeDefined();
});

it('should allow requests from allowed countries', () => {
const allowedCountries = ['US', 'CA'];
const ip = '192.168.0.1';

interceptor['allowedCountries'] = allowedCountries;

const result = interceptor['isCountryAllowed'](ip);

expect(result).toBe(true);
});

it('should deny requests from disallowed countries', () => {
const allowedCountries = ['US', 'CA'];
const ip = '192.168.0.1';

interceptor['allowedCountries'] = allowedCountries;

const result = interceptor['isCountryAllowed'](ip);

expect(result).toBe(false);
});
import {
ExecutionContext,
HttpStatus,
CallHandler,
HttpException,
InternalServerErrorException,
Logger,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { Test, TestingModule } from '@nestjs/testing';
import { Observable, of } from 'rxjs';

describe('GeoIPInterceptor', () => {
let geoIPInterceptor: GeoIPInterceptor;
let httpService: HttpService;
let logger: Logger;

beforeEach(async () => {
const allowedCountries = ['India'];

const mockHttpService = {
axiosRef: {
get: jest.fn(),
},
};

const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: HttpService,
useValue: mockHttpService,
},
ConfigService,
{
provide: Logger,
useValue: {
verbose: jest.fn(),
error: jest.fn(),
log: jest.fn(),
},
},
{
provide: GeoIPInterceptor,
useFactory: () => new GeoIPInterceptor(allowedCountries),
},
],
}).compile();

geoIPInterceptor = module.get<GeoIPInterceptor>(GeoIPInterceptor);
httpService = module.get<HttpService>(HttpService);
logger = module.get<Logger>(Logger);
});

it('should throw an error for invalid IP address', () => {
const allowedCountries = ['US', 'CA'];
const ip = 'invalid-ip';
it('should be defined', () => {
expect(geoIPInterceptor).toBeDefined();
});

interceptor['allowedCountries'] = allowedCountries;
it('should return the location data from the API for India', async () => {
const geoData = { country: 'India', regionName: 'Maharashtra' };
(httpService.axiosRef.get as jest.Mock).mockResolvedValueOnce({
data: geoData,
});

expect(() => interceptor['isCountryAllowed'](ip)).toThrowError();
const result = await geoIPInterceptor.getLocation('203.194.97.144');
expect(result.country).toEqual(geoData.country);
expect(result.regionName).toEqual(geoData.regionName);
});

it('should return the location for a valid IP address', async () => {
const ip = '192.168.0.1';
const expectedLocation = { country: 'US', city: 'New York' };

jest
.spyOn(interceptor, 'getLocation')
.mockResolvedValue(expectedLocation);
it('should return the location data from the API for USA', async () => {
const geoData = { country: 'United States', regionName: 'California' };
(httpService.axiosRef.get as jest.Mock).mockResolvedValueOnce({
data: geoData,
});

const result = await interceptor['getLocation'](ip);

expect(result).toEqual(expectedLocation);
const result = await geoIPInterceptor.getLocation('104.166.80.160');
expect(result.country).toEqual(geoData.country);
expect(result.regionName).toEqual(geoData.regionName);
});

it('should throw an error for an invalid IP address', async () => {
const ip = 'invalid-ip';

jest
.spyOn(interceptor, 'getLocation')
.mockRejectedValue(new Error('Invalid IP'));
it('should throw an error if the API call fails', async () => {
(httpService.axiosRef.get as jest.Mock).mockRejectedValueOnce(new Error());

await expect(interceptor['getLocation'](ip)).rejects.toThrowError(
'Invalid IP',
);
await expect(
geoIPInterceptor.getLocation('127.0.0.1'),
).rejects.toThrow(InternalServerErrorException);
});
});
});
Loading

0 comments on commit a5a72cf

Please sign in to comment.