Skip to content

Commit

Permalink
test: add basic storage service proxy tests
Browse files Browse the repository at this point in the history
  • Loading branch information
EmiM committed Apr 2, 2024
1 parent 6e35699 commit 7e6b4bc
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Test, TestingModule } from '@nestjs/testing'
import { TestModule } from '../common/test.module'
import { ServerProxyServiceModule } from './storageServerProxy.module'
import { ServerProxyService } from './storageServerProxy.service'
import { ServerStoredCommunityMetadata } from './storageServerProxy.types'
import { jest } from '@jest/globals'
import { prepareResponse } from './testUtils'
import { createLibp2pAddress, getValidInvitationUrlTestData, validInvitationDatav1 } from '@quiet/common'

const mockFetch = async (responseData: Partial<Response>[]) => {
/** Mock fetch responses and then initialize nest service */
const mockedFetch = jest.fn(() => {
return Promise.resolve(prepareResponse(responseData[0]))
})

for (const data of responseData) {
mockedFetch.mockResolvedValueOnce(prepareResponse(data))
}

global.fetch = mockedFetch
const module = await Test.createTestingModule({
imports: [ServerProxyServiceModule],
}).compile()
return { service: module.get<ServerProxyService>(ServerProxyService), module }
}

describe('Server Proxy Service', () => {
let testingModule: TestingModule
let clientMetadata: ServerStoredCommunityMetadata
beforeEach(() => {
const data = getValidInvitationUrlTestData(validInvitationDatav1[0]).data
clientMetadata = {
id: '12345678',
ownerCertificate: 'MIIDeTCCAyCgAwIBAgIGAYv8J0ToMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMTB21hYzIzMT',
rootCa: 'MIIBUjCB+KADAgECAgEBMAoGCCqGSM49BAMCMBIxEDAOBgNVBAM',
ownerOrbitDbIdentity: data.ownerOrbitDbIdentity,
peerList: [createLibp2pAddress(data.pairs[0].onionAddress, data.pairs[0].peerId)],
psk: data.psk,
}
})

afterEach(async () => {
jest.clearAllMocks()
await testingModule.close()
})

it('downloads data for existing cid and proper server address', async () => {
const { module, service } = await mockFetch([
{ status: 200, json: () => Promise.resolve({ access_token: 'secretToken' }) },
{ status: 200, json: () => Promise.resolve(clientMetadata) },
])
testingModule = module
service.setServerAddress('http://whatever')
const data = await service.downloadData('cid')
expect(data).toEqual(clientMetadata)
expect(global.fetch).toHaveBeenCalledTimes(2)
})

it('obtains token', async () => {
const expectedToken = 'verySecretToken'
const { module, service } = await mockFetch([
{ status: 200, json: () => Promise.resolve({ access_token: expectedToken }) },
])
testingModule = module
service.setServerAddress('http://whatever')
const token = await service.auth()
expect(token).toEqual(expectedToken)
expect(global.fetch).toHaveBeenCalledTimes(1)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,33 @@ import EventEmitter from 'events'
import { ServerStoredCommunityMetadata } from './storageServerProxy.types'
import fetchRetry from 'fetch-retry'
import Logger from '../common/logger'
const fetch = fetchRetry(global.fetch)
// TODO: handle retries

class HTTPResponseError extends Error {
response: Response
constructor(message: string, response: Response) {
super(`${message}: ${response.status} ${response.statusText}`)
this.response = response
}
}

@Injectable()
export class ServerProxyService extends EventEmitter {
private readonly logger = Logger(ServerProxyService.name)
serverAddress: string
_serverAddress: string
fetch: any

constructor() {
super()
this.fetch = fetchRetry(global.fetch)
}

get serverAddress() {
if (!this._serverAddress) throw new Error('Server address is required')
return this._serverAddress
}

setServerAddress = (serverAddress: string) => {
this.serverAddress = serverAddress
this._serverAddress = serverAddress
}

get authUrl() {
Expand All @@ -33,9 +47,9 @@ export class ServerProxyService extends EventEmitter {
return `Bearer ${token}`
}

auth = async () => {
auth = async (): Promise<string> => {
this.logger('Authenticating')
const authResponse = await fetch(this.authUrl, {
const authResponse = await this.fetch(this.authUrl, {
method: 'POST',
})
this.logger('Auth response status', authResponse.status)
Expand All @@ -46,11 +60,15 @@ export class ServerProxyService extends EventEmitter {
public downloadData = async (cid: string): Promise<ServerStoredCommunityMetadata> => {
this.logger('Downloading data', cid)
const accessToken = await this.auth()
const dataResponse = await fetch(this.getInviteUrl(cid), {
const dataResponse: Response = await this.fetch(this.getInviteUrl(cid), {
method: 'GET',
headers: { Authorization: this.getAuthorizationHeader(accessToken) },
retries: 3,
})
this.logger('Download data response status', dataResponse.status)
if (!dataResponse.ok) {
throw new HTTPResponseError('Failed to download data', dataResponse)
}
const data: ServerStoredCommunityMetadata = await dataResponse.json()
this.logger('Downloaded data', data)
return data
Expand All @@ -59,7 +77,7 @@ export class ServerProxyService extends EventEmitter {
public uploadData = async (cid: string, data: ServerStoredCommunityMetadata) => {
this.logger('Uploading data', cid, data)
const accessToken = await this.auth()
const dataResponsePost = await fetch(this.getInviteUrl(cid), {
const dataResponse: Response = await this.fetch(this.getInviteUrl(cid), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Expand All @@ -68,7 +86,10 @@ export class ServerProxyService extends EventEmitter {
body: JSON.stringify(data),
retries: 3,
})
this.logger('Upload data response status', dataResponsePost)
this.logger('Upload data response status', dataResponse.status)
if (!dataResponse.ok) {
throw new HTTPResponseError('Failed to upload data', dataResponse)
}
return cid
}
}
34 changes: 34 additions & 0 deletions packages/backend/src/nest/storageServerProxy/testUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const prepareResponse = (responseData: Partial<Response>) => {
const ok = responseData.status ? responseData.status >= 200 && responseData.status < 300 : false
const response: Response = {
headers: new Headers(),
ok,
redirected: false,
status: 200,
statusText: '',
type: 'basic',
url: '',
clone: function (): Response {
throw new Error('Function not implemented.')
},
body: null,
bodyUsed: false,
arrayBuffer: function (): Promise<ArrayBuffer> {
throw new Error('Function not implemented.')
},
blob: function (): Promise<Blob> {
throw new Error('Function not implemented.')
},
formData: function (): Promise<FormData> {
throw new Error('Function not implemented.')
},
json: function (): Promise<any> {
throw new Error('Function not implemented.')
},
text: function (): Promise<string> {
throw new Error('Function not implemented.')
},
...responseData,
}
return response
}
9 changes: 0 additions & 9 deletions packages/common/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,3 @@ export function getValidInvitationUrlTestData<T extends InvitationDataV1 | Invit
data: data,
}
}

// export const getValidInvitationUrlTestData = (data: InvitationData) => {
// return {
// shareUrl: () => composeInvitationShareUrl(data),
// deepUrl: () => composeInvitationDeepUrl(data),
// code: () => composeInvitationShareUrl(data).split(QUIET_JOIN_PAGE + '#')[1],
// data: data,
// }
// }

0 comments on commit 7e6b4bc

Please sign in to comment.