Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tests] Add e2e test for auth/login #133

Merged
merged 10 commits into from
May 14, 2020
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@types/supertest": "^2.0.8",
"@typescript-eslint/eslint-plugin": "^2.23.0",
"@typescript-eslint/parser": "^2.23.0",
"axios": "^0.19.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
Expand Down
6 changes: 4 additions & 2 deletions src/auth/dto/login-normal-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsNumber } from 'class-validator'
import { IsInt, Min, Max } from 'class-validator'

export class LoginNormalUserRequestDto {
@ApiProperty()
@IsNumber()
@IsInt()
@Min(0)
@Max(47)
prefecture: number
}
10 changes: 7 additions & 3 deletions src/users/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { IsString, IsNumber, IsOptional, IsNotEmpty } from 'class-validator'
import { IsString, IsOptional, IsNotEmpty, IsInt, Max, Min } from 'class-validator'

export class CreateUserDto {
@ApiProperty()
Expand All @@ -10,14 +10,18 @@ export class CreateUserDto {

export class CreateUserProfileDto {
@ApiProperty()
@IsNumber()
@IsInt()
@Min(0)
@Max(47)
prefecture: number
}

export class UpdateUserProfileDto {
@ApiPropertyOptional({ example: 14 })
@IsOptional()
@IsNumber()
@IsInt()
@Min(0)
@Max(47)
prefecture: number

// Keys without any decorators are non-Whitelisted. Validator will throw error if it's passed in payload.
Expand Down
14 changes: 12 additions & 2 deletions test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,33 @@ import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication } from '@nestjs/common'
import * as request from 'supertest'
import { AppModule } from './../src/app.module'
import * as firebaseAdmin from 'firebase-admin'

describe('AppController (e2e)', () => {
let app: INestApplication

beforeEach(async () => {
beforeAll(async (done) => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile()

app = moduleFixture.createNestApplication()
await app.init()

done()
})

it('/ (GET)', () => {
it('/ (GET)', (done) => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!')
.end(() => done())
})

afterAll(async (done) => {
await firebaseAdmin.app().delete()
await app.close()
done()
})
})
97 changes: 87 additions & 10 deletions test/auth.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,46 @@ import * as request from 'supertest'
import { AppModule } from './../src/app.module'
import { ConfigService } from '@nestjs/config'
import * as firebaseAdmin from 'firebase-admin'
import { generateFirebaseDefaultToken } from './util'
import { generateFirebaseDefaultToken, deleteFirebaseTestUser } from './util'

describe('AppController (e2e)', () => {
describe('AuthController (e2e)', () => {
let app: INestApplication
let customToken: string
let firebaseDefaultToken: string
const testUIDNormalUser = 'E2E_TEST_UID_NORMAL_USER'

beforeAll(async () => {
beforeAll(async (done) => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile()

app = moduleFixture.createNestApplication()
await app.init()

// TODO @yashmurty : Add API Key to env file.
const configService = app.get(ConfigService)
const firebaseWebAPIKey = configService.get('FIREBASE_WEB_API_KEY')
console.log('firebaseWebAPIKey : ', firebaseWebAPIKey)
customToken = await firebaseAdmin.auth().createCustomToken('uid')
customToken = await firebaseAdmin.auth().createCustomToken(testUIDNormalUser, {
// eslint-disable-next-line @typescript-eslint/camelcase
provider_id: 'anonymous',
})

await generateFirebaseDefaultToken(customToken, firebaseWebAPIKey)
firebaseDefaultToken = await generateFirebaseDefaultToken(customToken, firebaseWebAPIKey)

done()
})

it('/auth/login (POST) - No Auth bearer', () => {
it('/auth/login (POST) - No Auth bearer', (done) => {
return request(app.getHttpServer())
.post('/auth/login')
.expect(401)
.expect((response) => {
expect(response.body.error).toEqual('Unauthorized')
expect(response.body.message).toEqual('No bearer token found in the header')
})
.end(() => done())
})

it('/auth/login (POST) - Invalid Auth bearer', () => {
it('/auth/login (POST) - Invalid Auth bearer', (done) => {
return request(app.getHttpServer())
.post('/auth/login')
.set('Authorization', 'Bearer RANDOM_STRING')
Expand All @@ -46,9 +52,10 @@ describe('AppController (e2e)', () => {
expect(response.body.error).toEqual('Unauthorized')
expect(response.body.message).toContain('Decoding Firebase ID token failed')
})
.end(() => done())
})

it('/auth/login (POST) - CustomToken Auth bearer', () => {
it('/auth/login (POST) - CustomToken Auth bearer', (done) => {
return request(app.getHttpServer())
.post('/auth/login')
.set('Authorization', `Bearer ${customToken}`)
Expand All @@ -57,5 +64,75 @@ describe('AppController (e2e)', () => {
expect(response.body.error).toEqual('Unauthorized')
expect(response.body.message).toContain('expects an ID token, but was given a custom token')
})
.end(() => done())
})

it('/auth/login (POST) - FDT Auth bearer without prefecture', (done) => {
return request(app.getHttpServer())
.post('/auth/login')
.set('Authorization', `Bearer ${firebaseDefaultToken}`)
.expect(400)
.expect((response) => {
expect(response.body.error).toEqual('Bad Request')
expect(response.body.message).toBeInstanceOf(Array)
expect(response.body.message).toContain('prefecture must be an integer number')
})
.end(() => done())
})

it('/auth/login (POST) - FDT Auth bearer with invalid prefecture', (done) => {
return request(app.getHttpServer())
.post('/auth/login')
.set('Authorization', `Bearer ${firebaseDefaultToken}`)
.send({
prefecture: 'random_value',
})
.expect(400)
.expect((response) => {
expect(response.body.error).toEqual('Bad Request')
expect(response.body.message).toBeInstanceOf(Array)
expect(response.body.message).toContain('prefecture must be an integer number')
})
.end(() => done())
})

it('/auth/login (POST) - FDT Auth bearer with invalid prefecture', (done) => {
return request(app.getHttpServer())
.post('/auth/login')
.set('Authorization', `Bearer ${firebaseDefaultToken}`)
.send({
prefecture: 100,
})
.expect(400)
.expect((response) => {
expect(response.body.error).toEqual('Bad Request')
expect(response.body.message).toBeInstanceOf(Array)
expect(response.body.message).toContain('prefecture must not be greater than 47')
})
.end(() => done())
})

it('/auth/login (POST) - FDT Auth bearer with valid prefecture', (done) => {
return request(app.getHttpServer())
.post('/auth/login')
.set('Authorization', `Bearer ${firebaseDefaultToken}`)
.send({
prefecture: 1,
})
.expect(200)
.expect((response) => {
expect(response.body).toEqual({})
expect(response.body).toBeInstanceOf(Object)
expect(response.body).toMatchObject({})
})
.end(() => done())
})

afterAll(async (done) => {
await deleteFirebaseTestUser(testUIDNormalUser)

await firebaseAdmin.app().delete()
await app.close()
done()
})
})
63 changes: 51 additions & 12 deletions test/util.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
import * as request from 'supertest'

// TODO @yashmurty : WIP.
export async function generateFirebaseDefaultToken(customToken: string, firebaseWebAPIKey: string) {
await request(`https://identitytoolkit.googleapis.com/`)
.post(`/v1/accounts:signInWithCustomToken?key=${firebaseWebAPIKey}`)
.send({
token: customToken,
returnSecureToken: true,
import axios from 'axios'
import * as firebaseAdmin from 'firebase-admin'

/**
* Generate firebase default token. Uses the custom token generated via
* Firebase Admin SDK and returns firebase idToken.
* @param customToken
* @param firebaseWebAPIKey
*/
export async function generateFirebaseDefaultToken(
customToken: string,
firebaseWebAPIKey: string
): Promise<string> {
let firebaseDefaultToken: string

await axios
.post(
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=
${firebaseWebAPIKey}`,
{
token: customToken,
returnSecureToken: true,
}
)
.then(function(response) {
firebaseDefaultToken = response.data.idToken
})
.set('Content-Type', 'application/json')
.expect((response) => {
console.log(response.body)
.catch(function(error) {
console.log(error.response.data.error)
throw error
})

return firebaseDefaultToken
}

/**
* Delete the test user from firebase auth and firestore.
* @param testUIDNormalUser
*/
export async function deleteFirebaseTestUser(testUIDNormalUser: string): Promise<void> {
await firebaseAdmin.auth().deleteUser(testUIDNormalUser)
await firebaseAdmin
.firestore()
.collection('users')
.doc(testUIDNormalUser)
.collection('profile')
.doc(testUIDNormalUser)
.delete()
await firebaseAdmin
.firestore()
.collection('users')
.doc(testUIDNormalUser)
.delete()
}