diff --git a/src/application/service/access-sharing/CreateAccessPass.ts b/src/application/service/access-sharing/CreateAccessPass.ts index 29128d5..4549518 100644 --- a/src/application/service/access-sharing/CreateAccessPass.ts +++ b/src/application/service/access-sharing/CreateAccessPass.ts @@ -20,7 +20,7 @@ export class CreateAccessPass { throw new AccessPassFailedError(AccessPassFailureReason.SHARING_CODE_EXPIRED); } - const accessPass = new AccessPass(new UserId(userId), sharingCode.userId); + const accessPass = new AccessPass(new UserId(userId), sharingCode.userId, sharingCode.accessDuration); return this.accessPassRepository.save(accessPass); } diff --git a/src/application/service/index.ts b/src/application/service/index.ts index 0e1a9c3..12ebd75 100644 --- a/src/application/service/index.ts +++ b/src/application/service/index.ts @@ -26,6 +26,7 @@ import { import { DokobitAuthenticationProvider } from '../../infrastructure/idAuthentication/DokobitAuthenticationProvider'; import { GetUser } from './users/GetUser'; import { UpdateUser } from './users/UpdateUser'; +import { GetAccessibleUsers } from './users/GetAccessibleUsers'; import { GetCountries } from './users/GetCountries'; import { GetTestTypes } from './tests/GetTestTypes'; import { CreateSharingCode } from './access-sharing/CreateSharingCode'; @@ -95,8 +96,8 @@ export const createMagicLink = new CreateNewMagicLink( ); export const getUser = new GetUser(userRepository); - export const updateUser = new UpdateUser(userRepository); +export const getAccessibleUsers = new GetAccessibleUsers(userRepository, accessPassRepository); export const getExistingOrCreateNewUser = new GetExistingOrCreateNewUser( userRepository, diff --git a/src/application/service/users/GetAccessibleUsers.ts b/src/application/service/users/GetAccessibleUsers.ts new file mode 100644 index 0000000..468d08f --- /dev/null +++ b/src/application/service/users/GetAccessibleUsers.ts @@ -0,0 +1,18 @@ +import { User } from '../../../domain/model/user/User'; +import { UserId } from '../../../domain/model/user/UserId'; +import { UserRepository } from '../../../domain/model/user/UserRepository'; +import { AccessPassRepository } from '../../../domain/model/accessPass/AccessPassRepository'; + +export class GetAccessibleUsers { + constructor(private userRepository: UserRepository, private accessPassRepository: AccessPassRepository) {} + + async byActorId(id: string): Promise> { + const accessPasses = await this.accessPassRepository.findByActorId(new UserId(id)); + + const userIds = accessPasses + .filter((accessPass) => !accessPass.isExpired()) + .map((accessPass) => accessPass.subjectUserId); + + return this.userRepository.findByUserIds(userIds); + } +} diff --git a/src/database/migrations/V8__addDurationToAccessPass.ts b/src/database/migrations/V8__addDurationToAccessPass.ts new file mode 100644 index 0000000..deb0ca5 --- /dev/null +++ b/src/database/migrations/V8__addDurationToAccessPass.ts @@ -0,0 +1,21 @@ +import knex from 'knex'; + +const ACCESS_PASS_TABLE = 'access_pass'; + +export async function up(db: knex) { + await db.schema.table(ACCESS_PASS_TABLE, (table) => { + table.integer('duration'); + }); + + await db(ACCESS_PASS_TABLE).where({ duration: null }).update({ duration: 60 }); + + await db.schema.alterTable(ACCESS_PASS_TABLE, (table) => { + table.integer('duration').notNullable().alter(); + }); +} + +export async function down(db: knex) { + await db.schema.alterTable(ACCESS_PASS_TABLE, (table) => { + table.dropColumn('duration'); + }); +} diff --git a/src/domain/model/accessPass/AccessPass.test.ts b/src/domain/model/accessPass/AccessPass.test.ts index 3e478cf..d123a2b 100644 --- a/src/domain/model/accessPass/AccessPass.test.ts +++ b/src/domain/model/accessPass/AccessPass.test.ts @@ -11,7 +11,7 @@ describe('Access Pass', () => { const actorUserId = new UserId(); const subjectUserId = new UserId(); - const accessPass = new AccessPass(actorUserId, subjectUserId); + const accessPass = new AccessPass(actorUserId, subjectUserId, 120); expect(accessPass.actorUserId).toBe(actorUserId); expect(accessPass.subjectUserId).toBe(subjectUserId); @@ -25,11 +25,15 @@ describe('Access Pass', () => { MockDate.set('2020-11-03 00:00:00'); - const accessPass = new AccessPass(actorUserId, subjectUserId); + const accessPass = new AccessPass(actorUserId, subjectUserId, 120); + + // More than default duration + MockDate.set('2020-11-03 01:01:00'); expect(accessPass.isExpired()).toBe(false); - MockDate.set('2020-11-03 01:01:00'); + // More than specified duration + MockDate.set('2020-11-03 02:01:00'); expect(accessPass.isExpired()).toBe(true); }); diff --git a/src/domain/model/accessPass/AccessPass.ts b/src/domain/model/accessPass/AccessPass.ts index 77c51a5..8434210 100644 --- a/src/domain/model/accessPass/AccessPass.ts +++ b/src/domain/model/accessPass/AccessPass.ts @@ -1,13 +1,13 @@ import { v4 as uuidv4 } from 'uuid'; import { UserId } from '../user/UserId'; -// 1 hour -const ACCESS_REQUEST_LIFETIME_MSEC = 60 * 60 * 1_000; +const DEFAULT_DURATION_MINUTES = 60; export class AccessPass { constructor( readonly actorUserId: UserId, readonly subjectUserId: UserId, + readonly duration: number = DEFAULT_DURATION_MINUTES, readonly id: string = uuidv4(), readonly creationTime: Date = new Date() ) {} @@ -17,6 +17,7 @@ export class AccessPass { } public expirationTime(): Date { - return new Date(this.creationTime.getTime() + ACCESS_REQUEST_LIFETIME_MSEC); + const millisecondDuration = this.duration * 60 * 1_000; + return new Date(this.creationTime.getTime() + millisecondDuration); } } diff --git a/src/domain/model/accessPass/AccessPassRepository.ts b/src/domain/model/accessPass/AccessPassRepository.ts index 726e74e..3564c2c 100644 --- a/src/domain/model/accessPass/AccessPassRepository.ts +++ b/src/domain/model/accessPass/AccessPassRepository.ts @@ -5,4 +5,6 @@ export interface AccessPassRepository { save(accessPass: AccessPass): Promise; findByUserIds(actorUserId: UserId, subjectUserId: UserId): Promise; + + findByActorId(actorUserId: UserId): Promise>; } diff --git a/src/domain/model/user/UserRepository.ts b/src/domain/model/user/UserRepository.ts index 9fce754..66855da 100644 --- a/src/domain/model/user/UserRepository.ts +++ b/src/domain/model/user/UserRepository.ts @@ -8,6 +8,7 @@ export interface UserRepository { save(user: User): Promise; findByUserId(userId: UserId): Promise; + findByUserIds(userIds: Array): Promise>; findByAuthenticationDetails(authenticationDetails: AuthenticationDetails): Promise; } diff --git a/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts b/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts index 5405983..badee56 100644 --- a/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts +++ b/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts @@ -4,19 +4,21 @@ import { AccessPass } from '../../domain/model/accessPass/AccessPass'; import { UserId } from '../../domain/model/user/UserId'; import { v4 as uuidv4 } from 'uuid'; import { cleanupDatabase } from '../../test/cleanupDatabase'; +import MockDate from 'mockdate'; describe('PsqlAccessPassRepository', () => { const psqlAccessPassRepository = new PsqlAccessPassRepository(database); beforeEach(async () => { await cleanupDatabase(); + MockDate.reset(); }); it('inserts new and retrieves a access pass', async () => { const actorUserId = new UserId(); const subjectUserId = new UserId(); - const accessPass = new AccessPass(actorUserId, subjectUserId); + const accessPass = new AccessPass(actorUserId, subjectUserId, 120); await psqlAccessPassRepository.save(accessPass); const persistedAccessPass = await psqlAccessPassRepository.findByUserIds(actorUserId, subjectUserId); @@ -28,11 +30,9 @@ describe('PsqlAccessPassRepository', () => { const actorUserId = new UserId(); const subjectUserId = new UserId(); - const firstPass = new AccessPass(actorUserId, subjectUserId, uuidv4(), new Date('2020-01-01')); - - const secondPass = new AccessPass(actorUserId, subjectUserId, uuidv4(), new Date('2020-01-02')); - - const thirdPass = new AccessPass(actorUserId, subjectUserId, uuidv4(), new Date('2020-01-03')); + const firstPass = new AccessPass(actorUserId, subjectUserId, 60, uuidv4(), new Date('2020-01-01')); + const secondPass = new AccessPass(actorUserId, subjectUserId, 120, uuidv4(), new Date('2020-01-02')); + const thirdPass = new AccessPass(actorUserId, subjectUserId, 180, uuidv4(), new Date('2020-01-03')); await psqlAccessPassRepository.save(firstPass); await psqlAccessPassRepository.save(thirdPass); @@ -42,6 +42,32 @@ describe('PsqlAccessPassRepository', () => { expect(persistedAccessPass).toEqual(thirdPass); }); + + it('retrieves all access passes for the actor with latet first', async () => { + const actorUserId = new UserId(); + + MockDate.set('2020-01-01T00:00:00Z'); + const firstPass = new AccessPass(actorUserId, new UserId(), 60); + + MockDate.set('2020-01-01T00:00:01Z'); + const secondPass = new AccessPass(actorUserId, new UserId(), 120); + + MockDate.set('2020-01-01T00:00:02Z'); + const thirdPass = new AccessPass(actorUserId, new UserId(), 180); + + await psqlAccessPassRepository.save(firstPass); + await psqlAccessPassRepository.save(thirdPass); + await psqlAccessPassRepository.save(secondPass); + + MockDate.set('2020-01-01T01:01:00Z'); + + const accessPasses = await psqlAccessPassRepository.findByActorId(actorUserId); + + expect(accessPasses.length).toBe(3); + expect(accessPasses[0]).toEqual(thirdPass); + expect(accessPasses[1]).toEqual(secondPass); + expect(accessPasses[2]).toEqual(firstPass); + }); }); afterAll(() => { diff --git a/src/infrastructure/persistence/PsqlAccessPassRepository.ts b/src/infrastructure/persistence/PsqlAccessPassRepository.ts index 8ee6315..d1a3a4a 100644 --- a/src/infrastructure/persistence/PsqlAccessPassRepository.ts +++ b/src/infrastructure/persistence/PsqlAccessPassRepository.ts @@ -5,17 +5,20 @@ import { UserId } from '../../domain/model/user/UserId'; const ACCESS_PASS_TABLE_NAME = 'access_pass'; +const COLUMNS = [ + 'id', + 'actor_user_id as actorUserId', + 'subject_user_id as subjectUserId', + 'duration', + 'creation_time as creationTime', +]; + export class PsqlAccessPassRepository implements AccessPassRepository { constructor(private db: knex) {} async findByUserIds(actorUserId: UserId, subjectUserId: UserId) { const linkRow: any = await this.db(ACCESS_PASS_TABLE_NAME) - .select([ - 'id', - 'actor_user_id as actorUserId', - 'subject_user_id as subjectUserId', - 'creation_time as creationTime', - ]) + .select(COLUMNS) .where({ actor_user_id: actorUserId.value, subject_user_id: subjectUserId.value, @@ -27,25 +30,37 @@ export class PsqlAccessPassRepository implements AccessPassRepository { return null; } - return new AccessPass( - new UserId(linkRow.actorUserId), - new UserId(linkRow.subjectUserId), - linkRow.id, - linkRow.creationTime - ); + return convertRowToAccessPass(linkRow); + } + + async findByActorId(actorUserId: UserId) { + // TODO group by userId to get latest, but also get latest duration, so can't just use MAX + const rows: any = await this.db(ACCESS_PASS_TABLE_NAME) + .select(COLUMNS) + .where({ + actor_user_id: actorUserId.value, + }) + .orderBy('creation_time', 'desc'); + + if (!rows) { + return []; + } + + return rows.map(convertRowToAccessPass); } async save(accessPass: AccessPass) { return await this.db .raw( ` - insert into "${ACCESS_PASS_TABLE_NAME}" (id, actor_user_id, subject_user_id, creation_time) - values (:id, :actor_user_id, :subject_user_id, :creation_time) + insert into "${ACCESS_PASS_TABLE_NAME}" (id, actor_user_id, subject_user_id, duration, creation_time) + values (:id, :actor_user_id, :subject_user_id, :duration, :creation_time) `, { id: accessPass.id, actor_user_id: accessPass.actorUserId.value, subject_user_id: accessPass.subjectUserId.value, + duration: accessPass.duration, creation_time: accessPass.creationTime, } ) @@ -54,3 +69,13 @@ export class PsqlAccessPassRepository implements AccessPassRepository { }); } } + +function convertRowToAccessPass(linkRow: any) { + return new AccessPass( + new UserId(linkRow.actorUserId), + new UserId(linkRow.subjectUserId), + linkRow.duration, + linkRow.id, + linkRow.creationTime + ); +} diff --git a/src/infrastructure/persistence/PsqlUserRepository.ts b/src/infrastructure/persistence/PsqlUserRepository.ts index c47830e..e1bee9a 100644 --- a/src/infrastructure/persistence/PsqlUserRepository.ts +++ b/src/infrastructure/persistence/PsqlUserRepository.ts @@ -97,6 +97,12 @@ export class PsqlUserRepository implements UserRepository { return this.extractUserAndPopulateRoles(userRow); } + async findByUserIds(userIds: Array): Promise> { + const idValues = userIds.map((userId) => userId.value); + const rows: any = await this.db(USER_TABLE_NAME).whereIn('id', idValues).select(USER_TABLE_COLUMNS); + return rows.map(mapRowToUser); + } + private async saveRoleAssignment(roleAssignment: RoleAssignmentAction, context = this.db) { await context('role_to_user_assignment').insert({ id: roleAssignment.id.value, @@ -110,18 +116,7 @@ export class PsqlUserRepository implements UserRepository { } private async extractUserAndPopulateRoles(userRow: any) { - const user = new User( - new UserId(userRow.id), - new AuthenticationDetails( - new AuthenticationMethod(userRow.authenticationMethod), - new AuthenticationIdentifier(userRow.authenticationIdentifier) - ), - userRow.email ? new Email(userRow.email) : undefined, - fromDbProfile(userRow.profile as DbProfile | undefined), - fromDbAddress(userRow.address as DbAddress | undefined), - userRow.creationTime, - userRow.modificationTime - ); + const user = mapRowToUser(userRow); const roleAssignmentActions = await this.getRoleAssignments(user); Reflect.set(user.roleAssignments, 'assignmentActions', roleAssignmentActions); Reflect.set(user.roleAssignments, 'newAssignmentActions', []); @@ -188,6 +183,21 @@ export class PsqlUserRepository implements UserRepository { } } +function mapRowToUser(userRow: any): User { + return new User( + new UserId(userRow.id), + new AuthenticationDetails( + new AuthenticationMethod(userRow.authenticationMethod), + new AuthenticationIdentifier(userRow.authenticationIdentifier) + ), + userRow.email ? new Email(userRow.email) : undefined, + fromDbProfile(userRow.profile as DbProfile | undefined), + fromDbAddress(userRow.address as DbAddress | undefined), + userRow.creationTime, + userRow.modificationTime + ); +} + function toDbProfile(profile?: Profile): DbProfile | undefined { return profile ? { diff --git a/src/presentation/api/access-sharing/AccessPassController.test.ts b/src/presentation/api/access-sharing/AccessPassController.test.ts index c5e932f..3e8fe51 100644 --- a/src/presentation/api/access-sharing/AccessPassController.test.ts +++ b/src/presentation/api/access-sharing/AccessPassController.test.ts @@ -10,6 +10,7 @@ import { getTokenForUser } from '../../../test/authentication'; import { v4 as uuidv4 } from 'uuid'; import { aUserWithAllInformation } from '../../../test/domainFactories'; import { RootController } from '../RootController'; +import MockDate from 'mockdate'; describe('sharing code endpoints', () => { const app = new RootController().expressApp(); @@ -68,9 +69,11 @@ describe('sharing code endpoints', () => { await userRepository.save(user1); await userRepository.save(user2); - const sharingCode = new SharingCode(user2.id, 60, uuidv4(), new Date()); + const sharingCode = new SharingCode(user2.id, 120, uuidv4(), new Date()); await sharingCodeRepository.save(sharingCode); + MockDate.set('2020-05-04T00:00:00Z'); + await request(app) .post(`/api/v1/users/${user1.id.value}/access-passes`) .send({ code: sharingCode.code }) @@ -79,7 +82,7 @@ describe('sharing code endpoints', () => { .expect((response) => { const accessPass = response.body; expect(accessPass.userId).toBe(user2.id.value); - expect(accessPass.expiryTime).toBeDefined(); + expect(accessPass.expiryTime).toBe('2020-05-04T02:00:00.000Z'); }); }); }); diff --git a/src/presentation/api/tests/TestController.test.ts b/src/presentation/api/tests/TestController.test.ts index 6b92333..0450008 100644 --- a/src/presentation/api/tests/TestController.test.ts +++ b/src/presentation/api/tests/TestController.test.ts @@ -85,7 +85,7 @@ describe('TestController', () => { const subjectUser = await userRepository.save(aNewUser()); await testRepository.save(aTest(subjectUser.id)); - const accessPass = new AccessPass(actorUser.id, subjectUser.id, uuidv4(), new Date('1970-01-01')); + const accessPass = new AccessPass(actorUser.id, subjectUser.id, 60, uuidv4(), new Date('1970-01-01')); await accessPassRepository.save(accessPass); diff --git a/src/presentation/api/users/UserController.test.ts b/src/presentation/api/users/UserController.test.ts index 0ddf65f..95af957 100644 --- a/src/presentation/api/users/UserController.test.ts +++ b/src/presentation/api/users/UserController.test.ts @@ -7,18 +7,22 @@ import { accessPassRepository, userRepository } from '../../../infrastructure/pe import { User } from '../../../domain/model/user/User'; import { AccessPass } from '../../../domain/model/accessPass/AccessPass'; import { CREATE_USERS } from '../../../domain/model/authentication/Permissions'; + import { anAddress, aNewUser, aUserWithAllInformation, + anAccessPass, magicLinkAuthenticationDetails, estonianIdAuthenticationDetails, } from '../../../test/domainFactories'; + import { persistedUserWithRoleAndPermissions } from '../../../test/persistedEntities'; import { getTokenForUser } from '../../../test/authentication'; import { anApiAddress, anApiProfile } from '../../../test/apiFactories'; import { RootController } from '../RootController'; import { AuthenticationMethodType } from '../../../domain/model/user/AuthenticationMethod'; +import MockDate from 'mockdate'; describe('user endpoints', () => { const app = new RootController().expressApp(); @@ -105,6 +109,31 @@ describe('user endpoints', () => { }); }); + describe('GET /users', () => { + it('returns users for whom the actor has a pass', async () => { + const actor = await userRepository.save(aNewUser()); + + const userWithAccess = await userRepository.save(aNewUser()); + const userWithExpiredAccess = await userRepository.save(aNewUser()); + const userWithoutAccess = await userRepository.save(aNewUser()); + + MockDate.set('2020-01-01T00:00:00Z'); + await accessPassRepository.save(anAccessPass(actor.id, userWithAccess.id, 60)); + await accessPassRepository.save(anAccessPass(actor.id, userWithExpiredAccess.id, 15)); + MockDate.set('2020-01-01T00:30:00Z'); + + await request(app) + .get(`/api/v1/users`) + .set({ Authorization: `Bearer ${await getTokenForUser(actor)}` }) + .expect(200) + .expect((response) => { + const users = response.body; + expect(users.length).toEqual(1); + expect(userWithAccess.id.value).toEqual(userWithAccess.id.value); + }); + }); + }); + describe('GET /users/:id', () => { it('returns 404 if user is not found', async () => { const user = await userRepository.save(aNewUser()); @@ -178,7 +207,7 @@ describe('user endpoints', () => { const actorUser = await userRepository.save(aNewUser()); const subjectUser = await userRepository.save(aNewUser()); - const accessPass = new AccessPass(actorUser.id, subjectUser.id, uuidv4(), new Date('1970-01-01')); + const accessPass = new AccessPass(actorUser.id, subjectUser.id, 60, uuidv4(), new Date('1970-01-01')); await accessPassRepository.save(accessPass); diff --git a/src/presentation/api/users/UserController.ts b/src/presentation/api/users/UserController.ts index e733064..d0b6c9c 100644 --- a/src/presentation/api/users/UserController.ts +++ b/src/presentation/api/users/UserController.ts @@ -1,10 +1,16 @@ -import { accessManagerFactory, getUser, updateUser, getExistingOrCreateNewUser } from '../../../application/service'; +import { + accessManagerFactory, + getUser, + updateUser, + getExistingOrCreateNewUser, + getAccessibleUsers, +} from '../../../application/service'; import { UserTransformer } from '../../transformers/UserTransformer'; import { UserId } from '../../../domain/model/user/UserId'; import { ApiError, apiErrorCodes } from '../../dtos/ApiError'; -import { UserDTO, RestrictedUserDTO } from '../../dtos/users/UserDTO'; +import { UserDTO, RestrictedUserDTO, SharedUserDTO } from '../../dtos/users/UserDTO'; import { Authorized, @@ -19,6 +25,7 @@ import { UseAfter, UseBefore, } from 'routing-controllers'; + import { hasPermission } from '../../middleware/hasPermission'; import { CREATE_USERS } from '../../../domain/model/authentication/Permissions'; import { UserErrorHandler } from './UserErrorHandler'; @@ -55,14 +62,28 @@ export class UserController { return this.userTransformer.toRestrictedUserDTO(user); } + @Get('') + async getUsers(@CurrentUser({ required: true }) actor: User): Promise> { + const users = await getAccessibleUsers.byActorId(actor.id.value); + return users.map(this.userTransformer.toSharedUserDTO); + } + @Get('/:id') - async getById(@Param('id') idValue: string, @CurrentUser({ required: true }) actor: User): Promise { + async getById( + @Param('id') idValue: string, + @CurrentUser({ required: true }) actor: User + ): Promise { const id = new UserId(idValue); await this.validateCanGetUser(actor, id); const user = await getUser.byId(idValue); + // Share less data if requesting another user + if (id.value !== actor.id.value) { + return this.userTransformer.toSharedUserDTO(user); + } + return this.userTransformer.toUserDTO(user); } diff --git a/src/presentation/dtos/users/UserDTO.ts b/src/presentation/dtos/users/UserDTO.ts index 3c1dd09..af0c662 100644 --- a/src/presentation/dtos/users/UserDTO.ts +++ b/src/presentation/dtos/users/UserDTO.ts @@ -15,3 +15,9 @@ export interface RestrictedUserDTO { id: string; authenticationDetails: AuthenticationDetailsDTO; } + +export interface SharedUserDTO { + id: string; + profile?: ProfileDTO; + address?: AddressDTO; +} diff --git a/src/presentation/transformers/UserTransformer.ts b/src/presentation/transformers/UserTransformer.ts index fbbe925..a4bd3fc 100644 --- a/src/presentation/transformers/UserTransformer.ts +++ b/src/presentation/transformers/UserTransformer.ts @@ -1,7 +1,7 @@ import { User } from '../../domain/model/user/User'; import { Address } from '../../domain/model/user/Address'; import { Profile } from '../../domain/model/user/Profile'; -import { UserDTO, RestrictedUserDTO } from '../dtos/users/UserDTO'; +import { UserDTO, RestrictedUserDTO, SharedUserDTO } from '../dtos/users/UserDTO'; import { ProfileDTO } from '../dtos/users/ProfileDTO'; import { AddressDTO } from '../dtos/users/AddressDTO'; import { AuthenticationDetails } from '../../domain/model/user/AuthenticationDetails'; @@ -19,6 +19,14 @@ export class UserTransformer { }; } + public toSharedUserDTO(user: User): SharedUserDTO { + return { + id: user.id.value, + profile: user.profile ? this.toProfileDTO(user.profile) : undefined, + address: user.address ? this.toAddressDTO(user.address) : undefined, + }; + } + public toRestrictedUserDTO(user: User): RestrictedUserDTO { return { id: user.id.value, diff --git a/src/test/domainFactories.ts b/src/test/domainFactories.ts index c48033e..b39d13a 100644 --- a/src/test/domainFactories.ts +++ b/src/test/domainFactories.ts @@ -14,6 +14,7 @@ import { TestTypeId } from '../domain/model/test/testType/TestTypeId'; import { Test } from '../domain/model/test/Test'; import { TestId } from '../domain/model/test/TestId'; import { Results } from '../domain/model/test/Results'; +import { AccessPass } from '../domain/model/accessPass/AccessPass'; import { ConfidenceLevel } from '../domain/model/test/ConfidenceLevel'; import { InterpretationRules } from '../domain/model/test/interpretation/InterpretationRules'; import { InterpretationTheme } from '../domain/model/test/interpretation/Interpretation'; @@ -101,6 +102,10 @@ export function aResult(userId = new UserId(), details = { c: true, igg: true, i return new Results(userId, details, ConfidenceLevel.LOW, notes); } +export function anAccessPass(actorId: UserId, subjectId: UserId, duration: number = 60) { + return new AccessPass(actorId, subjectId, duration); +} + export function antibodyTestType() { return new TestType( new TestTypeId(),