import { App , AppState } from "../app" ; import { Server , ServerConfig } from "../server" ; import { StubMessenger } from "../messenger" ; import { MFAMessage , InviteCreatedMessage , MemberAddedMessage } from "../messages" ; import { DirectSender } from "../transport" ; import { MemoryStorage } from "../storage" ; import { MemoryAttachmentStorage } from "../attachment" ; import { ErrorCode } from "../error" ; import { OrgType } from "../org" ; import { Logger } from "../log" ; import { MFAPurpose } from "../mfa" ; import { Spec , assertResolve , assertReject } from "./spec" ; export function appSpec ( ) { console . log ( "testing app" ) ; const clientUrl = "https://padloc.app" ; const messenger = new StubMessenger ( ) ; const server = new Server ( new ServerConfig ( { clientUrl , reportErrors : "support@padloc.app" } ) , new MemoryStorage ( ) , messenger , new Logger ( new MemoryStorage ( ) ) , new MemoryAttachmentStorage ( ) ) ; const app = new App ( new DirectSender ( server ) ) ; const otherApp = new App ( new DirectSender ( server ) ) ; const user = { email : "lengden@olga.com" , name : "Lengden Olga" , password : "correct battery horse staple" } ; const otherUser = { email : "max@mustermann.com" , name : "Max Mustermann" , password : "password" } ; let sharedVaultID = "" ; return ( test , assert ) => { test ( "App initializes successfully" , async ( ) => { await app . loaded ; } ) ; test ( "Signup" , async ( ) => { await app . requestMFACode ( user . email , MFAPurpose . Signup ) ; const message = messenger . lastMessage ( user . email ) ; assert . instanceOf ( message , MFAMessage ) ; const code = ( message ! as MFAMessage ) . request . code ; const { token } = await app . retrieveMFAToken ( user . email , code , MFAPurpose . Signup ) ; await app . signup ( { ... user , verify : token } ) ; assert . isFalse ( app . state . locked , "App should be in unlocked state after signup." ) ; assert . isNotNull ( app . account , "Account object should be populated after signup." ) ; const account = app . account ! ; assert . ownInclude ( account , { email : user . email , name : user . name } , "Account info should be set correctly." ) ; assert . isNotNull ( app . mainVault , "Main vault should be created." ) ; } ) ; test ( "Create Personal Vault Item" , async ( ) => { const item = await app . createItem ( "My First Item" , app . mainVault ! ) ; assert . equal ( app . mainVault ! . items . size , 1 , "Item count should be 1." ) ; assert . ok ( app . getItem ( item . id ) , "Item should be accessible by ID." ) ; assert . equal ( app . getItem ( item . id ) ! . item , item ) ; assert . equal ( app . getItem ( item . id ) ! . vault , app . mainVault ) ; } ) ; test ( "Create Org" , async ( ) => { const org = await app . createOrg ( "My Org" , OrgType . Business ) ; assert . equal ( org . name , "My Org" , "Organization name should be correct." ) ; assert . ok ( org . id , "Organization ID should be set." ) ; assert . isTrue ( org . isOwner ( app . account ! ) , "Account should be organization owner." ) ; assert . equal ( app . state . orgs . length , 1 ) ; await assertResolve ( assert , ( ) => app . account ! . verifyOrg ( app . state . orgs [ 0 ] ) , "Organization should be verified successfully." ) ; } ) ; test ( "Create Vault" , async ( ) => { const name = "My Shared Vault" ; const vault = await app . createVault ( name , app . state . orgs [ 0 ] , [ { id : app . account ! . id , readonly : false } ] ) ; assert . equal ( vault . name , name ) ; await app . synchronize ( ) ; assert . equal ( app . state . vaults . length , 2 ) ; } ) ; test ( "Invite Member" , async ( ) => { let org = app . state . orgs [ 0 ] ; let [ invite ] = await app . createInvites ( org , [ otherUser . email ] ) ; const { secret } = invite ; assert . equal ( invite . email , otherUser . email ) ; const inviteMessage = messenger . lastMessage ( otherUser . email ) as InviteCreatedMessage ; assert . instanceOf ( inviteMessage , InviteCreatedMessage ) ; const linkPattern = new RegExp ( `${ clientUrl }/invite/${ org . id }/${ invite . id }\\?email=(.*)&verify=(.*)` ) ; assert . match ( inviteMessage . link , linkPattern ) ; const [ , email , verify ] = inviteMessage . link . match ( linkPattern ) ! ; assert . equal ( email , invite . email ) ; await otherApp . signup ( { ... otherUser , verify } ) ; invite = ( await otherApp . getInvite ( org . id , invite . id ) ) ! ; assert . isTrue ( await otherApp . acceptInvite ( invite , secret ) ) ; invite = ( await app . getInvite ( org . id , invite . id ) ) ! ; invite . secret = secret ; assert . isTrue ( await invite . verifyInvitee ( ) ) ; await app . confirmInvite ( invite ) ; assert . isTrue ( app . state . orgs [ 0 ] . isMember ( otherApp . account ! ) ) ; await otherApp . synchronize ( ) ; assert . equal ( otherApp . state . orgs . length , 1 ) ; assert . isTrue ( otherApp . state . orgs [ 0 ] . isMember ( otherApp . account ! ) ) ; const addedMessage = messenger . lastMessage ( otherUser . email ) as MemberAddedMessage ; assert . instanceOf ( addedMessage , MemberAddedMessage ) ; assert . equal ( addedMessage . org . id , org . id ) ; } ) ; test ( "Create Group" , async ( ) => { const org = app . state . orgs [ 0 ] ; await app . createGroup ( org , "Everyone" , org . members ) ! ; const group = app . state . orgs [ 0 ] . getGroup ( "Everyone" ) ! ; assert . ok ( group ) ; const vault = await app . createVault ( "Another Vault" , app . state . orgs [ 0 ] , [ ] , [ { name : group . name , readonly : false } ] ) ; sharedVaultID = vault . id ; await otherApp . synchronize ( ) ; assert . equal ( otherApp . vaults . length , 2 ) ; } ) ; test ( "Simulataneous Edit" , async ( ) => { const [ item1 , item2 ] = await Promise . all ( [ app . createItem ( "Added Item 1" , { id : sharedVaultID } ) , otherApp . createItem ( "Added Item 2" , { id : sharedVaultID } ) ] ) ; await Promise . all ( [ app . syncVault ( { id : sharedVaultID } ) , otherApp . syncVault ( { id : sharedVaultID } ) ] ) ; await Promise . all ( [ app . syncVault ( { id : sharedVaultID } ) , otherApp . syncVault ( { id : sharedVaultID } ) ] ) ; assert . ok ( app . getItem ( item2 . id ) , "Created item from second app should should show up in first app" ) ; assert . ok ( otherApp . getItem ( item1 . id ) , "Created item from first app should should show up in second app" ) ; await app . updateItem ( item1 , { name : "Edited Item" } ) ; const item3 = await app . createItem ( "Added Item 3" , app . getVault ( sharedVaultID ) ! ) ; await otherApp . deleteItems ( [ item2 ] ) ; await Promise . all ( [ app . syncVault ( { id : sharedVaultID } ) , otherApp . syncVault ( { id : sharedVaultID } ) ] ) ; await Promise . all ( [ app . syncVault ( { id : sharedVaultID } ) , otherApp . syncVault ( { id : sharedVaultID } ) ] ) ; assert . isNull ( app . getItem ( item2 . id ) ) ; assert . ok ( otherApp . getItem ( item3 . id ) , "Created Item show up other instance" ) ; assert . equal ( otherApp . getItem ( item1 . id ) ! . item . name , "Edited Item" ) ; } ) ; test ( "Lock" , async ( ) => { await app . lock ( ) ; assert . isTrue ( app . state . locked , "App should be in 'locked' state." ) ; assert . isNotOk ( app . account ! . privateKey , "Private key should be inaccessible after locking." ) ; assert . equal ( app . mainVault ! . items . size , 0 , "Main vault should be inacessible after locking." ) ; } ) ; test ( "Unlock" , async ( ) => { await app . unlock ( user . password ) ; assert . isFalse ( app . state . locked , "App should be in 'unlocked' state." ) ; assert . instanceOf ( app . account ! . privateKey , Uint8Array , "Private key should be loaded." ) ; assert . isNotNull ( app . mainVault , "Main vault should be loaded." ) ; assert . equal ( app . mainVault ! . items . size , 1 , "Items should be loaded." ) ; } ) ; test ( "Logout" , async ( ) => { await app . logout ( ) ; const state = await app . storage . get ( AppState , app . state . id ) ; assert . isNotOk ( state . account , "Account should be unloaded." ) ; assert . isNotOk ( state . session , "Session should be unloaded." ) ; assert . equal ( state . orgs . length , 0 , "Orgs should be unloaded." ) ; assert . equal ( state . vaults . length , 0 , "Vaults should be unloaded." ) ; } ) ; test ( "Login" , async ( ) => { const app = new App ( new DirectSender ( server ) ) ; await assertReject ( assert , ( ) => app . login ( user . email , user . password ) , ErrorCode . MFA_REQUIRED , "Logging in from a new device should require email verification." ) ; await app . requestMFACode ( user . email , MFAPurpose . Login ) ; const message = messenger . lastMessage ( user . email ) ; const code = ( message ! as MFAMessage ) . request . code ; const { token } = await app . retrieveMFAToken ( user . email , code , MFAPurpose . Login ) ; await app . login ( user . email , user . password , token ) ; assert . isNotNull ( app . account , "Account should be loaded." ) ; const account = app . account ! ; assert . ownInclude ( account , { email : user . email , name : user . name } , "Account info should be correct." ) ; assert . equal ( app . mainVault ! . items . size , 1 , "Vault Items should be loaded" ) ; } ) ; } ; }