Skip to content

Commit cf38801

Browse files
committed
refactor(accounts): implement GetAccountHandler
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent 039ee1b commit cf38801

File tree

6 files changed

+180
-20
lines changed

6 files changed

+180
-20
lines changed

src/subdomains/accounts/accounts.module.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Account from '#accounts/entities/account.entity'
88
import JwtOptionsFactory from '#accounts/factories/jwt-options.factory'
99
import CreateAccountHandler from '#accounts/handlers/create-account.handler'
1010
import DeleteAccountHandler from '#accounts/handlers/delete-account.handler'
11+
import GetAccountHandler from '#accounts/handlers/get-account.handler'
1112
import AccountsRepository from '#accounts/providers/accounts.repository'
1213
import AuthService from '#accounts/services/auth.service'
1314
import JwtStrategy from '#accounts/strategies/jwt.strategy'
@@ -31,6 +32,7 @@ import { JwtModule } from '@nestjs/jwt'
3132
AuthService,
3233
CreateAccountHandler,
3334
DeleteAccountHandler,
35+
GetAccountHandler,
3436
JwtOptionsFactory,
3537
JwtStrategy
3638
]

src/subdomains/accounts/controllers/__tests__/accounts.controller.spec.mts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Account from '#accounts/entities/account.entity'
1111
import JwtOptionsFactory from '#accounts/factories/jwt-options.factory'
1212
import CreateAccountHandler from '#accounts/handlers/create-account.handler'
1313
import DeleteAccountHandler from '#accounts/handlers/delete-account.handler'
14+
import GetAccountHandler from '#accounts/handlers/get-account.handler'
1415
import AccountsRepository from '#accounts/providers/accounts.repository'
1516
import AuthService from '#accounts/services/auth.service'
1617
import DependenciesModule from '#modules/dependencies.module'
@@ -37,7 +38,8 @@ describe('unit:accounts/controllers/AccountsController', () => {
3738
AccountsRepository,
3839
AuthService,
3940
CreateAccountHandler,
40-
DeleteAccountHandler
41+
DeleteAccountHandler,
42+
GetAccountHandler
4143
]
4244
}).compile()
4345

src/subdomains/accounts/guards/existing-account.guard.mts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
* @module sneusers/accounts/guards/ExistingAccount
44
*/
55

6-
import AccountsRepository from '#accounts/providers/accounts.repository'
7-
import {
8-
MissingAccountException
9-
} from '@flex-development/sneusers/accounts/errors'
6+
import GetAccountQuery from '#accounts/queries/get-account.query'
107
import {
118
Injectable,
129
type CanActivate,
1310
type ExecutionContext
1411
} from '@nestjs/common'
12+
import { QueryBus } from '@nestjs/cqrs'
1513
import type { FastifyRequest } from 'fastify'
1614

1715
/**
@@ -25,10 +23,10 @@ class ExistingAccountGuard implements CanActivate {
2523
/**
2624
* Create a new existing account guard.
2725
*
28-
* @param {AccountsRepository} accounts
29-
* User accounts repository
26+
* @param {QueryBus} queries
27+
* The query bus
3028
*/
31-
constructor(protected accounts: AccountsRepository) {}
29+
constructor(protected queries: QueryBus) {}
3230

3331
/**
3432
* Check `request.params.uid` references an existing account.
@@ -43,8 +41,6 @@ class ExistingAccountGuard implements CanActivate {
4341
* Object containing details about the current request pipeline
4442
* @return {Promise<true>}
4543
* Whether the current request is allowed to proceed
46-
* @throws {MissingAccountException}
47-
* If an account is not found
4844
*/
4945
public async canActivate(context: ExecutionContext): Promise<true> {
5046
/**
@@ -54,16 +50,9 @@ class ExistingAccountGuard implements CanActivate {
5450
*/
5551
const req: FastifyRequest = context.switchToHttp().getRequest()
5652

57-
/**
58-
* Unique account id.
59-
*
60-
* @const {string} uid
61-
*/
62-
const uid: string = String(req.params.uid)
63-
64-
if (!await this.accounts.findById(uid)) {
65-
throw new MissingAccountException(uid)
66-
}
53+
// check for existing user account.
54+
// the query handler will throw if an account is not found.
55+
await this.queries.execute(new GetAccountQuery(String(req.params.uid)))
6756

6857
return true
6958
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @file Unit Tests - GetAccountHandler
3+
* @module sneusers/accounts/handlers/tests/unit/GetAccount
4+
*/
5+
6+
import Account from '#accounts/entities/account.entity'
7+
import MissingAccountException from '#accounts/errors/missing-account.exception'
8+
import TestSubject from '#accounts/handlers/get-account.handler'
9+
import AccountsRepository from '#accounts/providers/accounts.repository'
10+
import GetAccountQuery from '#accounts/queries/get-account.query'
11+
import AccountFactory from '#tests/utils/account.factory'
12+
import Seeder from '#tests/utils/seeder'
13+
import type { AccountDocument } from '@flex-development/sneusers/accounts'
14+
import DatabaseModule from '@flex-development/sneusers/database'
15+
import { Test, type TestingModule } from '@nestjs/testing'
16+
import { ObjectId } from 'bson'
17+
18+
describe('unit:accounts/handlers/GetAccountHandler', () => {
19+
let ref: TestingModule
20+
let seeder: Seeder<AccountDocument>
21+
let subject: TestSubject
22+
23+
afterAll(async () => {
24+
await seeder.down()
25+
})
26+
27+
beforeAll(async () => {
28+
ref = await Test.createTestingModule({
29+
imports: [DatabaseModule.forFeature(Account)],
30+
providers: [AccountsRepository, TestSubject]
31+
}).compile()
32+
33+
seeder = new Seeder(new AccountFactory(), ref.get(AccountsRepository))
34+
subject = ref.get(TestSubject)
35+
36+
await seeder.up(1)
37+
})
38+
39+
describe('#execute', () => {
40+
it('should return user account referenced by `query.uid`', async () => {
41+
// Arrange
42+
const account: Account = new Account(seeder.seeds[0]!)
43+
const query: GetAccountQuery = new GetAccountQuery(account.uid)
44+
45+
// Act
46+
const result = await subject.execute(query)
47+
48+
// Expect
49+
expect(result).to.be.instanceof(Account).and.eql(account)
50+
})
51+
52+
it('should throw if an account is not found', async () => {
53+
// Arrange
54+
let error!: MissingAccountException
55+
56+
// Act
57+
try {
58+
await subject.execute(new GetAccountQuery(new ObjectId()))
59+
} catch (e: unknown) {
60+
error = e as typeof error
61+
}
62+
63+
// Expect
64+
expect(error).to.be.instanceof(MissingAccountException)
65+
})
66+
})
67+
})
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @file Handlers - GetAccountHandler
3+
* @module sneusers/accounts/handlers/GetAccount
4+
*/
5+
6+
import Account from '#accounts/entities/account.entity'
7+
import MissingAccountException from '#accounts/errors/missing-account.exception'
8+
import AccountsRepository from '#accounts/providers/accounts.repository'
9+
import GetAccountQuery from '#accounts/queries/get-account.query'
10+
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'
11+
12+
/**
13+
* Account query handler.
14+
*
15+
* @class
16+
* @implements {IQueryHandler<GetAccountQuery>}
17+
*/
18+
@QueryHandler(GetAccountQuery)
19+
class GetAccountHandler implements IQueryHandler<GetAccountQuery> {
20+
/**
21+
* Create a new account query handler.
22+
*
23+
* @param {AccountsRepository} accounts
24+
* User accounts repository
25+
*/
26+
constructor(protected accounts: AccountsRepository) {}
27+
28+
/**
29+
* Get a user account by id.
30+
*
31+
* Fails if an account is not found.
32+
*
33+
* @public
34+
* @instance
35+
* @async
36+
*
37+
* @param {GetAccountQuery} query
38+
* The query to execute
39+
* @return {Promise<Account>}
40+
* The user account referenced by {@linkcode query.uid}
41+
* @throws {MissingAccountException}
42+
* If an account is not found
43+
*/
44+
public async execute(query: GetAccountQuery): Promise<Account> {
45+
/**
46+
* The user account referenced by {@linkcode query.uid}.
47+
*
48+
* @const {Account | null} account
49+
*/
50+
const account: Account | null = await this.accounts.findById(query.uid)
51+
52+
// throw on missing account.
53+
if (!account) throw new MissingAccountException(query.uid)
54+
55+
return account
56+
}
57+
}
58+
59+
export default GetAccountHandler
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @file Queries - GetAccountQuery
3+
* @module sneusers/accounts/queries/GetAccount
4+
*/
5+
6+
import type { Account } from '@flex-development/sneusers/accounts'
7+
import { Query } from '@nestjs/cqrs'
8+
import { ApiProperty, ApiSchema } from '@nestjs/swagger'
9+
import type { ObjectId } from 'bson'
10+
11+
/**
12+
* Get account query.
13+
*
14+
* @class
15+
* @extends {Query<Account>}
16+
*/
17+
@ApiSchema()
18+
class GetAccountQuery extends Query<Account> {
19+
/**
20+
* The id of the account to retrieve.
21+
*
22+
* @public
23+
* @instance
24+
* @member {string} uid
25+
*/
26+
@ApiProperty({ description: 'id of account to retrieve', type: 'string' })
27+
public uid!: string
28+
29+
/**
30+
* Create a new account query.
31+
*
32+
* @param {ObjectId | string | null | undefined} [uid]
33+
* The id of the account to retrieve
34+
*/
35+
constructor(uid?: ObjectId | string | null | undefined) {
36+
super()
37+
if (uid !== null && uid !== undefined) this.uid = String(uid)
38+
}
39+
}
40+
41+
export default GetAccountQuery

0 commit comments

Comments
 (0)