-
Notifications
You must be signed in to change notification settings - Fork 17
feat: implement control plane #3
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './logger-stub-adapter' | ||
export * from './repo-querier-stub-adapter' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { ForMonitoringAuthDetails } from '../../ports/drivens/for-monitoring' | ||
|
||
export class LoggerStubAdapter implements ForMonitoringAuthDetails { | ||
log(event: string, message: string) { | ||
console.log(event, message) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { ExternalUser } from '../../../repository/app/schemas' | ||
import { ForRepoQuerying } from '../../ports/drivens' | ||
|
||
const userMock: ExternalUser = { | ||
id: '1', | ||
name: 'John Doe', | ||
email: '[email protected]', | ||
admin: false, | ||
user: true, | ||
} | ||
|
||
export class RepoQuerierAdapter implements ForRepoQuerying { | ||
getUser(_email: string): Promise<ExternalUser> { | ||
return Promise.resolve(userMock) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ControlPlane } from '../../app/control-plane' | ||
import { AuthDetails, Permissions } from '../../app/schemas' | ||
import { ForManagingAuthDetails } from '../../ports/drivers/for-managing-auth-details' | ||
|
||
export class AuthDetailsProxy implements ForManagingAuthDetails { | ||
constructor(private readonly controlPlane: ControlPlane) {} | ||
|
||
async getAuthDetails(email: string, password: string): Promise<AuthDetails> { | ||
return this.controlPlane.getAuthDetails(email, password) | ||
} | ||
|
||
async getPermissions(email: string): Promise<Permissions> { | ||
return this.controlPlane.getPermissions(email) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './auth-details-proxy' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { LoggerStubAdapter } from '../adapters/drivens' | ||
import { RepoQuerierAdapter } from '../adapters/drivens/repo-querier-stub-adapter' | ||
import { AuthDetailsProxy } from '../adapters/drivers' | ||
import { ControlPlane } from './control-plane' | ||
|
||
const compositionMock = () => { | ||
const monitorStub = new LoggerStubAdapter() | ||
const repoQueryStub = new RepoQuerierAdapter() | ||
const dashboardApiMock = new ControlPlane(monitorStub, repoQueryStub) | ||
|
||
const authenticatorProxyAdapter = new AuthDetailsProxy(dashboardApiMock) | ||
|
||
return { | ||
authenticatorProxyAdapter, | ||
} | ||
} | ||
|
||
export const { authenticatorProxyAdapter } = compositionMock() | ||
|
||
const registerMock = { | ||
name: 'John', | ||
email: '[email protected]', | ||
password: 'password', | ||
} | ||
|
||
authenticatorProxyAdapter.getAuthDetails( | ||
registerMock.email, | ||
registerMock.password | ||
) | ||
authenticatorProxyAdapter.getPermissions(registerMock.email) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { describe, expect, it } from 'vitest' | ||
import { RepoQuerierAdapter } from '../adapters/drivens/repo-querier-stub-adapter' | ||
import { LoggerStubAdapter } from '../adapters/drivens' | ||
import { ControlPlane } from './control-plane' | ||
import { AuthDetails, Permissions } from './schemas' | ||
|
||
describe('ControlPlane', () => { | ||
const monitorStub = new LoggerStubAdapter() | ||
const repoQueryStub = new RepoQuerierAdapter() | ||
const controPlaneMock = new ControlPlane(monitorStub, repoQueryStub) | ||
|
||
it.concurrent('should not expect a 123 token', async () => { | ||
//GIVEN | ||
const mockedParams = { | ||
email: `12`, | ||
password: '12345678', | ||
} | ||
|
||
const expectedResult: AuthDetails = { | ||
token: '123', | ||
refreshToken: '123', | ||
} | ||
|
||
//WHEN | ||
let result | ||
try { | ||
result = await controPlaneMock.getAuthDetails( | ||
mockedParams.email, | ||
mockedParams.password | ||
) | ||
} catch (error) {} | ||
|
||
//THEN | ||
expect(result).not.toEqual(expectedResult) | ||
}) | ||
|
||
it.concurrent('should get user permissions', async () => { | ||
//GIVEN | ||
|
||
const expectedResult: Permissions = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Realmente no me acuerdo si lo había dicho en el vídeo sobre tener dos properties para los roles, pero es posible que con solo saber si es admin o no sea suficiente, ya que si no es admin... es user. |
||
admin: false, | ||
user: true, | ||
} | ||
|
||
//WHEN | ||
const result = await controPlaneMock.getPermissions('[email protected]') | ||
|
||
//THEN | ||
expect(result).toEqual(expectedResult) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,72 @@ | ||||||||
import { ForRepoQuerying } from '../ports/drivens' | ||||||||
import { ForMonitoringAuthDetails } from '../ports/drivens/for-monitoring' | ||||||||
import { ForManagingAuthDetails } from '../ports/drivers/for-managing-auth-details' | ||||||||
import { AuthDetails, Permissions } from './schemas' | ||||||||
import jwt from 'jsonwebtoken' | ||||||||
|
||||||||
export class ControlPlane implements ForManagingAuthDetails { | ||||||||
private secretKey: string = 'mySecretKey' | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dejar espacios entre unidades lógicas
Suggested change
|
||||||||
constructor( | ||||||||
private readonly logger: ForMonitoringAuthDetails, | ||||||||
private readonly repoQuerier: ForRepoQuerying | ||||||||
) {} | ||||||||
|
||||||||
async getAuthDetails(email: string, password: string): Promise<AuthDetails> { | ||||||||
const generateToken = ( | ||||||||
payload: object, | ||||||||
secretKey: string, | ||||||||
expiresIn: string | ||||||||
): string => { | ||||||||
const token = jwt.sign(payload, secretKey, { expiresIn }) | ||||||||
|
||||||||
return token | ||||||||
} | ||||||||
const generateRefreshToken = ( | ||||||||
payload: object, | ||||||||
secretKey: string, | ||||||||
expiresIn: string | ||||||||
): string => { | ||||||||
const refreshToken = jwt.sign(payload, secretKey, { expiresIn }) | ||||||||
|
||||||||
return refreshToken | ||||||||
} | ||||||||
const token = generateToken({ email, password }, this.secretKey, '30m') | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dejar espacios entre unidades lógicas
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. '30m' podría ser una variable que puede ser modificable por algún método para que sea congruente en toda la app |
||||||||
if (!token) { | ||||||||
this.logger.log('Token creation', 'FAILED') | ||||||||
throw new Error('Failed creating token, please check credentials') | ||||||||
} | ||||||||
|
||||||||
const refreshToken = generateRefreshToken( | ||||||||
{ email, password }, | ||||||||
this.secretKey, | ||||||||
'1d' | ||||||||
) | ||||||||
|
||||||||
if (!refreshToken) { | ||||||||
this.logger.log('RefreshToken creation', 'FAILED') | ||||||||
throw new Error('Failed creating Refreshtoken, please check credentials') | ||||||||
} | ||||||||
|
||||||||
const result = { | ||||||||
token, | ||||||||
refreshToken, | ||||||||
} | ||||||||
|
||||||||
console.log('AUTHDETAILS', result) | ||||||||
|
||||||||
return { token, refreshToken } | ||||||||
} | ||||||||
|
||||||||
async getPermissions(email: string): Promise<Permissions> { | ||||||||
const user = await this.repoQuerier.getUser(email) | ||||||||
|
||||||||
const result = { | ||||||||
admin: user?.admin, | ||||||||
user: user?.user, | ||||||||
} | ||||||||
|
||||||||
console.log('PERMISSIONS', result) | ||||||||
|
||||||||
return { admin: user?.admin, user: user?.user } | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export interface AuthDetails { | ||
token: string | ||
refreshToken: string | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './details' | ||
export * from './permissions' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export interface Permissions { | ||
admin: boolean | ||
user: boolean | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export interface ForMonitoringAuthDetails { | ||
log(event: string, message: string): void | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { ExternalUser } from '../../../repository/app/schemas' | ||
|
||
export interface ForRepoQuerying { | ||
getUser(email: string): Promise<ExternalUser> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './for-monitoring' | ||
export * from './for-repo-querying' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { AuthDetails, Permissions } from '../../app/schemas' | ||
|
||
export interface ForManagingAuthDetails { | ||
getAuthDetails(email: string, password: string): Promise<AuthDetails> | ||
getPermissions(email: string): Promise<Permissions> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './for-managing-auth-details' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
export * from './control-authenticator-stub-adapter'; | ||
export * from './repo-querier-stub-adapter'; | ||
export * from './control-authenticator-stub-adapter' | ||
export * from './repo-querier-stub-adapter' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,21 @@ | ||
import { ExternalUser } from "../../../repository/app/schemas"; | ||
import { User } from "../../app/schemas"; | ||
import { ForRepoQuerying } from "../../ports/drivens"; | ||
import { ExternalUser } from '../../../repository/app/schemas' | ||
import { User } from '../../app/schemas' | ||
import { ForRepoQuerying } from '../../ports/drivens' | ||
|
||
const userMock: ExternalUser = { | ||
id: "1", | ||
name: "John Doe", | ||
email: "[email protected]", | ||
}; | ||
id: '1', | ||
name: 'John Doe', | ||
email: '[email protected]', | ||
admin: false, | ||
user: true, | ||
} | ||
|
||
export class RepoQuerierStub implements ForRepoQuerying { | ||
getUser(_email: string): Promise<ExternalUser> { | ||
return Promise.resolve(userMock); | ||
return Promise.resolve(userMock) | ||
} | ||
|
||
createUser(_user: User): Promise<ExternalUser> { | ||
return Promise.resolve(userMock); | ||
return Promise.resolve(userMock) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,75 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { ControlAuthenticatorStub } from "../adapters/drivens/control-authenticator-stub-adapter"; | ||
import { RepoQuerierStub } from "../adapters/drivens/repo-querier-stub-adapter"; | ||
import { DashboardApi } from "./dashboard-api"; | ||
import { AuthenticatedUser, User } from "./schemas"; | ||
import { describe, expect, it } from 'vitest' | ||
import { ControlAuthenticatorStub } from '../adapters/drivens/control-authenticator-stub-adapter' | ||
import { RepoQuerierStub } from '../adapters/drivens/repo-querier-stub-adapter' | ||
import { DashboardApi } from './dashboard-api' | ||
import { AuthenticatedUser, User } from './schemas' | ||
|
||
describe("DashboardApi", () => { | ||
const controlAuthenticatorStub = new ControlAuthenticatorStub(); | ||
const repoQuerierStub = new RepoQuerierStub(); | ||
describe('DashboardApi', () => { | ||
const controlAuthenticatorStub = new ControlAuthenticatorStub() | ||
const repoQuerierStub = new RepoQuerierStub() | ||
const dashboardApiMock = new DashboardApi( | ||
controlAuthenticatorStub, | ||
repoQuerierStub | ||
); | ||
) | ||
|
||
it.concurrent("should login", async () => { | ||
it.concurrent('should login', async () => { | ||
//GIVEN | ||
const mockedParams = { | ||
email: "[email protected]", | ||
password: "12345678", | ||
}; | ||
email: '[email protected]', | ||
password: '12345678', | ||
} | ||
|
||
const expectedResult: AuthenticatedUser = { | ||
id: "1", | ||
name: "John Doe", | ||
email: "[email protected]", | ||
token: "token", | ||
refreshToken: "refreshToken", | ||
id: '1', | ||
name: 'John Doe', | ||
email: '[email protected]', | ||
token: 'token', | ||
refreshToken: 'refreshToken', | ||
permissions: { | ||
admin: true, | ||
user: true, | ||
}, | ||
}; | ||
admin: false, | ||
user: true, | ||
} | ||
|
||
//WHEN | ||
const result = await dashboardApiMock.login( | ||
mockedParams.email, | ||
mockedParams.password | ||
); | ||
) | ||
|
||
//THEN | ||
expect(result).toEqual(expectedResult); | ||
}); | ||
expect(result).toEqual(expectedResult) | ||
}) | ||
|
||
it.concurrent("should register", async () => { | ||
it.concurrent('should register', async () => { | ||
//GIVEN | ||
|
||
const mockedUser: User = { | ||
name: "John", | ||
email: "[email protected]", | ||
password: "password", | ||
}; | ||
name: 'John', | ||
email: '[email protected]', | ||
password: 'password', | ||
} | ||
|
||
const expectedResult: AuthenticatedUser = { | ||
id: "1", | ||
name: "John Doe", | ||
email: "[email protected]", | ||
token: "token", | ||
refreshToken: "refreshToken", | ||
id: '1', | ||
name: 'John Doe', | ||
email: '[email protected]', | ||
token: 'token', | ||
refreshToken: 'refreshToken', | ||
permissions: { | ||
admin: true, | ||
user: true, | ||
}, | ||
}; | ||
admin: false, | ||
user: true, | ||
} | ||
|
||
//WHEN | ||
const result = await dashboardApiMock.register(mockedUser); | ||
const result = await dashboardApiMock.register(mockedUser) | ||
|
||
//THEN | ||
expect(result).toEqual(expectedResult); | ||
}); | ||
}); | ||
expect(result).toEqual(expectedResult) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Esto está bien para probar de manera local, pero lo eliminaría para prod ya que está probado mediante los test que esto funcione correctamente con datos de prueba