From 84888e6fe532ffd053ced45c3b4918468523ef9f Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 4 Sep 2019 13:16:54 +0100 Subject: [PATCH 1/4] feat(Agents): Merges xapi-agents service. --- package.json | 19 +- src/apps/activities/repo/storage/factory.ts | 4 +- src/apps/activities/repoFactory/index.ts | 2 +- src/apps/agents/app.ts | 6 +- src/apps/agents/azureStorageRepo/Config.ts | 6 + src/apps/agents/azureStorageRepo/clearRepo.ts | 27 ++ .../azureStorageRepo/deleteProfileContent.ts | 18 + .../azureStorageRepo/getProfileContent.ts | 20 + src/apps/agents/azureStorageRepo/index.ts | 17 + .../azureStorageRepo/storeProfileContent.ts | 48 ++ src/apps/agents/errors/Conflict.ts | 4 + src/apps/agents/errors/ExpiredClientError.ts | 8 + src/apps/agents/errors/IfMatch.ts | 4 + src/apps/agents/errors/IfNoneMatch.ts | 4 + src/apps/agents/errors/InvalidMethod.ts | 8 + src/apps/agents/errors/JsonSyntaxError.ts | 8 + src/apps/agents/errors/MaxEtags.ts | 4 + src/apps/agents/errors/MissingEtags.ts | 4 + src/apps/agents/errors/NonJsonObject.ts | 4 + .../agents/errors/UntrustedClientError.ts | 8 + src/apps/agents/expressPresenter/Config.ts | 12 + .../agents/expressPresenter/deleteProfile.ts | 12 + .../agents/expressPresenter/getFullAgent.ts | 20 + .../agents/expressPresenter/getProfiles.ts | 12 + src/apps/agents/expressPresenter/index.ts | 26 + .../agents/expressPresenter/postProfile.ts | 20 + .../agents/expressPresenter/putProfile.ts | 13 + .../alternateRequest/alternateRequest.test.ts | 33 ++ .../deleteProfile/alternateRequest.test.ts | 29 ++ .../deleteExistingProfile.test.ts | 22 + .../deleteNonExistingProfile.test.ts | 32 ++ .../deleteProfile/deleteOutsideOrg.test.ts | 23 + .../deleteProfile/deleteOutsideStore.test.ts | 23 + .../deleteProfile/deleteWithEtags.test.ts | 29 ++ .../deleteProfile/deleteWithScopes.test.ts | 29 ++ .../utils/assertOutsideClient.ts | 9 + .../deleteProfile/utils/deleteProfile.ts | 19 + .../getFullAgent/getNonExistingModel.test.ts | 69 +++ .../tests/getFullAgent/getWithScopes.test.ts | 29 ++ .../tests/getFullAgent/utils/getFullAgent.ts | 16 + .../tests/getProfile/alternateRequest.test.ts | 28 ++ .../getProfile/getExistingProfile.test.ts | 72 +++ .../getProfile/getNonExistingProfile.test.ts | 31 ++ .../tests/getProfile/getOutsideOrg.test.ts | 19 + .../tests/getProfile/getOutsideStore.test.ts | 19 + .../tests/getProfile/getWithScopes.test.ts | 29 ++ .../getProfile/utils/assertOutsideClient.ts | 6 + .../tests/getProfile/utils/getProfile.ts | 15 + .../getProfiles/alternateRequest.test.ts | 21 + .../getProfiles/getExistingProfiles.test.ts | 44 ++ .../getNonExistingProfiles.test.ts | 34 ++ .../tests/getProfiles/getOutsideOrg.test.ts | 19 + .../tests/getProfiles/getOutsideStore.test.ts | 19 + .../tests/getProfiles/getWithScopes.test.ts | 29 ++ .../tests/getProfiles/getWithSince.test.ts | 28 ++ .../getProfiles/utils/assertOutsideClient.ts | 6 + .../tests/getProfiles/utils/getProfiles.ts | 14 + .../postProfile/alternateRequest.test.ts | 39 ++ .../tests/postProfile/contentType.test.ts | 50 ++ .../postProfile/patchExistingJson.test.ts | 30 ++ .../postProfile/patchExistingObject.test.ts | 71 +++ .../postProfile/patchExistingText.test.ts | 30 ++ .../tests/postProfile/patchNewContent.test.ts | 56 +++ .../postProfile/patchOutsideClient.test.ts | 34 ++ .../tests/postProfile/patchWithEtags.test.ts | 44 ++ .../tests/postProfile/patchWithScopes.test.ts | 29 ++ .../tests/postProfile/utils/patchContent.ts | 6 + .../postProfile/utils/patchExistingProfile.ts | 15 + .../tests/postProfile/utils/patchProfile.ts | 26 + .../tests/putProfile/alternateRequest.test.ts | 34 ++ .../tests/putProfile/contentType.test.ts | 30 ++ .../overwriteExistingProfile.test.ts | 54 +++ .../overwriteNonExistingProfile.test.ts | 44 ++ .../putProfile/overwriteOutsideClient.test.ts | 31 ++ .../putProfile/overwriteWithEtags.test.ts | 54 +++ .../putProfile/overwriteWithScopes.test.ts | 29 ++ .../utils/overwriteExistingProfile.ts | 16 + .../putProfile/utils/overwriteProfile.ts | 27 ++ .../expressPresenter/tests/utils/setup.ts | 17 + .../expressPresenter/tests/utils/supertest.ts | 26 + .../utils/alternateProfileRequest.ts | 62 +++ .../expressPresenter/utils/catchErrors.ts | 22 + .../utils/contentTypePatterns.ts | 7 + .../utils/deleteProfileWithService.ts | 31 ++ .../agents/expressPresenter/utils/getAgent.ts | 14 + .../expressPresenter/utils/getClient.ts | 11 + .../expressPresenter/utils/getContentType.ts | 17 + .../agents/expressPresenter/utils/getEtag.ts | 6 + .../expressPresenter/utils/getHeader.ts | 8 + .../utils/getProfileFromService.ts | 33 ++ .../expressPresenter/utils/getProfileId.ts | 10 + .../utils/getProfilesFromService.ts | 29 ++ .../expressPresenter/utils/getWithService.ts | 22 + .../expressPresenter/utils/handleError.ts | 106 ++++ .../utils/overwriteProfileWithService.ts | 44 ++ .../utils/patchProfileWithService.ts | 44 ++ .../utils/translateWarning.ts | 13 + .../utils/validateVersionHeader.ts | 7 + src/apps/agents/fetchAuthRepo/Config.ts | 3 + src/apps/agents/fetchAuthRepo/getClient.ts | 36 ++ src/apps/agents/fetchAuthRepo/index.ts | 9 + src/apps/agents/googleStorageRepo/Config.ts | 7 + .../agents/googleStorageRepo/clearRepo.ts | 9 + .../googleStorageRepo/deleteProfileContent.ts | 12 + .../googleStorageRepo/getProfileContent.ts | 14 + src/apps/agents/googleStorageRepo/index.ts | 17 + .../googleStorageRepo/storeProfileContent.ts | 18 + src/apps/agents/localStorageRepo/Config.ts | 3 + .../localStorageRepo/deleteProfileContent.ts | 12 + .../localStorageRepo/getProfileContent.ts | 14 + src/apps/agents/localStorageRepo/index.ts | 15 + .../localStorageRepo/storeProfileContent.ts | 27 ++ src/apps/agents/memoryModelsRepo/Config.ts | 12 + .../agents/memoryModelsRepo/deleteProfile.ts | 51 ++ .../agents/memoryModelsRepo/getProfile.ts | 27 ++ .../agents/memoryModelsRepo/getProfiles.ts | 28 ++ .../agents/memoryModelsRepo/hasProfile.ts | 15 + src/apps/agents/memoryModelsRepo/index.ts | 21 + .../memoryModelsRepo/overwriteProfile.ts | 48 ++ .../agents/memoryModelsRepo/patchProfile.ts | 58 +++ .../memoryModelsRepo/utils/checkEtag.ts | 19 + .../memoryModelsRepo/utils/checkMaxEtags.ts | 7 + .../memoryModelsRepo/utils/createProfile.ts | 35 ++ .../memoryModelsRepo/utils/isMatchingAgent.ts | 22 + .../utils/matchProfileIdentifier.ts | 18 + .../utils/matchUniqueProfile.ts | 18 + src/apps/agents/models/Account.ts | 6 + src/apps/agents/models/Agent.ts | 10 + src/apps/agents/models/ClientModel.ts | 9 + src/apps/agents/models/Ifi.ts | 16 + src/apps/agents/models/Profile.ts | 16 + src/apps/agents/mongoAuthRepo/Config.ts | 7 + src/apps/agents/mongoAuthRepo/getClient.ts | 87 ++++ src/apps/agents/mongoAuthRepo/index.ts | 9 + .../mongoAuthRepo/tests/getClient.test.ts | 161 +++++++ src/apps/agents/mongoModelsRepo/Config.ts | 7 + .../agents/mongoModelsRepo/deleteProfile.ts | 48 ++ src/apps/agents/mongoModelsRepo/getProfile.ts | 29 ++ .../agents/mongoModelsRepo/getProfiles.ts | 22 + src/apps/agents/mongoModelsRepo/hasProfile.ts | 15 + src/apps/agents/mongoModelsRepo/index.ts | 24 + .../mongoModelsRepo/overwriteProfile.ts | 90 ++++ .../agents/mongoModelsRepo/patchProfile.ts | 103 ++++ .../agents/mongoModelsRepo/utils/constants.ts | 7 + .../mongoModelsRepo/utils/getAgentFilter.ts | 21 + .../mongoModelsRepo/utils/getEtagFilter.ts | 6 + .../mongoModelsRepo/utils/getProfileFilter.ts | 17 + .../utils/getProfilesFilter.ts | 17 + .../mongoModelsRepo/utils/getSinceFilter.ts | 6 + src/apps/agents/repo/auth/factory.ts | 6 +- src/apps/agents/repo/factory.ts | 2 +- src/apps/agents/repo/models/FactoryConfig.ts | 2 +- src/apps/agents/repo/models/factory.ts | 6 +- src/apps/agents/repo/storage/factory.ts | 14 +- src/apps/agents/repoFactory/AuthRepo.ts | 8 + src/apps/agents/repoFactory/ModelsRepo.ts | 23 + src/apps/agents/repoFactory/Repo.ts | 7 + src/apps/agents/repoFactory/StorageRepo.ts | 13 + src/apps/agents/repoFactory/index.ts | 130 +++++ .../options/DeleteProfileContentOptions.ts | 6 + .../options/DeleteProfileOptions.ts | 11 + .../repoFactory/options/GetClientOptions.ts | 5 + .../options/GetProfileContentOptions.ts | 6 + .../repoFactory/options/GetProfileOptions.ts | 10 + .../repoFactory/options/GetProfilesOptions.ts | 10 + .../repoFactory/options/HasProfileOptions.ts | 10 + .../options/OverwriteProfileOptions.ts | 16 + .../options/PatchProfileOptions.ts | 16 + .../options/StoreProfileContentOptions.ts | 7 + .../results/DeleteProfileResult.ts | 7 + .../repoFactory/results/GetClientResult.ts | 7 + .../results/GetProfileContentResult.ts | 5 + .../repoFactory/results/GetProfileResult.ts | 10 + .../repoFactory/results/GetProfilesResult.ts | 5 + .../repoFactory/results/HasProfileResult.ts | 5 + .../results/OverwriteProfileResult.ts | 6 + src/apps/agents/s3StorageRepo/Config.ts | 3 + .../s3StorageRepo/deleteProfileContent.ts | 14 + .../agents/s3StorageRepo/getProfileContent.ts | 20 + src/apps/agents/s3StorageRepo/index.ts | 15 + src/apps/agents/s3StorageRepo/readme.md | 7 + .../s3StorageRepo/storeProfileContent.ts | 15 + src/apps/agents/service/Config.ts | 7 + src/apps/agents/service/deleteProfile.ts | 38 ++ src/apps/agents/service/getClient.ts | 20 + src/apps/agents/service/getFullAgent.ts | 25 + src/apps/agents/service/getProfile.ts | 38 ++ src/apps/agents/service/getProfiles.ts | 30 ++ src/apps/agents/service/index.ts | 23 + src/apps/agents/service/overwriteProfile.ts | 62 +++ src/apps/agents/service/patchProfile.ts | 44 ++ .../deleteExistingProfile.test.ts | 51 ++ .../deleteNonExistingProfile.test.ts | 18 + .../deleteProfile/deleteOutsideOrg.test.ts | 23 + .../deleteProfile/deleteOutsideStore.test.ts | 23 + .../deleteProfile/deleteWithEtags.test.ts | 27 ++ .../deleteProfile/deleteWithScopes.test.ts | 20 + .../utils/assertOutsideClient.ts | 8 + .../deleteProfile/utils/deleteProfile.ts | 12 + .../getClient/getNonExistingClient.test.ts | 13 + .../getFullAgent/getNonExistingModel.test.ts | 61 +++ .../tests/getFullAgent/getWithScopes.test.ts | 22 + .../tests/getFullAgent/utils/getFullAgent.ts | 17 + .../getProfile/getExistingProfile.test.ts | 91 ++++ .../getProfile/getNonExistingProfile.test.ts | 22 + .../tests/getProfile/getOutsideOrg.test.ts | 19 + .../tests/getProfile/getOutsideStore.test.ts | 19 + .../tests/getProfile/getWithScopes.test.ts | 24 + .../getProfile/utils/assertOutsideClient.ts | 8 + .../getProfiles/getExistingProfiles.test.ts | 45 ++ .../getNonExistingProfiles.test.ts | 32 ++ .../tests/getProfiles/getOutsideOrg.test.ts | 19 + .../tests/getProfiles/getOutsideStore.test.ts | 19 + .../tests/getProfiles/getWithScopes.test.ts | 24 + .../tests/getProfiles/getWithSince.test.ts | 34 ++ .../getProfiles/utils/assertOutsideClient.ts | 7 + .../overwriteExistingProfile.ts | 54 +++ .../overwriteNonExistingProfile.test.ts | 33 ++ .../overwriteOutsideClient.test.ts | 31 ++ .../overwriteWithEtags.test.ts | 57 +++ .../overwriteWithScopes.test.ts | 21 + .../utils/overwriteProfile.ts | 25 + .../patchProfile/patchExistingJson.test.ts | 34 ++ .../patchProfile/patchExistingObject.test.ts | 74 +++ .../patchProfile/patchExistingText.test.ts | 34 ++ .../patchProfile/patchNewContent.test.ts | 48 ++ .../patchProfile/patchOutsideClient.test.ts | 29 ++ .../tests/patchProfile/patchWithEtags.test.ts | 41 ++ .../patchProfile/patchWithScopes.test.ts | 25 + .../tests/patchProfile/utils/patchContent.ts | 5 + .../tests/patchProfile/utils/patchProfile.ts | 24 + src/apps/agents/service/tests/utils/setup.ts | 9 + .../tests/validateAgent/validateAgent.test.ts | 88 ++++ .../service/utils/checkProfileReadScopes.ts | 6 + .../service/utils/checkProfileWriteScopes.ts | 6 + src/apps/agents/service/utils/createEtag.ts | 8 + .../agents/service/utils/validateAgent.ts | 31 ++ .../agents/service/utils/validateSince.ts | 6 + src/apps/agents/serviceFactory/Service.ts | 24 + src/apps/agents/serviceFactory/index.ts | 11 + .../options/DeleteProfileOptions.ts | 9 + .../options/GetClientOptions.ts | 3 + .../options/GetFullAgentOptions.ts | 7 + .../options/GetProfileOptions.ts | 8 + .../options/GetProfilesOptions.ts | 8 + .../options/OverwriteProfileOptions.ts | 12 + .../options/PatchProfileOptions.ts | 12 + .../serviceFactory/results/GetClientResult.ts | 5 + .../results/GetFullAgentResult.ts | 12 + .../results/GetProfileResult.ts | 6 + .../results/GetProfilesResult.ts | 3 + src/apps/agents/testAuthRepo/Config.ts | 3 + src/apps/agents/testAuthRepo/getClient.ts | 43 ++ src/apps/agents/testAuthRepo/index.ts | 9 + .../agents/tests/getFileExtension.test.ts | 21 + .../agents/translatorFactory/Translator.ts | 28 ++ src/apps/agents/translatorFactory/en.ts | 41 ++ src/apps/agents/translatorFactory/index.ts | 6 + src/apps/agents/utils/assertDeleted.ts | 16 + .../agents/utils/assertImmutableProfile.ts | 33 ++ src/apps/agents/utils/assertProfile.ts | 23 + src/apps/agents/utils/constants.ts | 3 + .../agents/utils/createImmutableProfile.ts | 10 + src/apps/agents/utils/createJsonProfile.ts | 12 + src/apps/agents/utils/createObjectProfile.ts | 11 + src/apps/agents/utils/createTextProfile.ts | 22 + src/apps/agents/utils/getFileExtension.ts | 13 + src/apps/agents/utils/getStorageDir.ts | 12 + src/apps/agents/utils/getTestProfile.ts | 17 + src/apps/agents/utils/getTestProfiles.ts | 15 + .../utils/overwriteProfileOutsideClient.ts | 6 + src/apps/agents/utils/parseJson.ts | 11 + .../agents/utils/patchProfileOutsideClient.ts | 19 + src/apps/agents/utils/scopes.ts | 28 ++ src/apps/agents/utils/testService.ts | 7 + src/apps/agents/utils/testValues.ts | 77 +++ .../repo/storageRepo/clearRepo/azure.ts | 2 +- .../storageRepo/utils/s3Storage/factory.ts | 2 +- src/apps/states/repo/storage/factory.ts | 4 +- src/apps/states/repoFactory/index.ts | 2 +- yarn.lock | 452 +++++++++--------- 281 files changed, 6592 insertions(+), 256 deletions(-) create mode 100644 src/apps/agents/azureStorageRepo/Config.ts create mode 100644 src/apps/agents/azureStorageRepo/clearRepo.ts create mode 100644 src/apps/agents/azureStorageRepo/deleteProfileContent.ts create mode 100644 src/apps/agents/azureStorageRepo/getProfileContent.ts create mode 100644 src/apps/agents/azureStorageRepo/index.ts create mode 100644 src/apps/agents/azureStorageRepo/storeProfileContent.ts create mode 100644 src/apps/agents/errors/Conflict.ts create mode 100644 src/apps/agents/errors/ExpiredClientError.ts create mode 100644 src/apps/agents/errors/IfMatch.ts create mode 100644 src/apps/agents/errors/IfNoneMatch.ts create mode 100644 src/apps/agents/errors/InvalidMethod.ts create mode 100644 src/apps/agents/errors/JsonSyntaxError.ts create mode 100644 src/apps/agents/errors/MaxEtags.ts create mode 100644 src/apps/agents/errors/MissingEtags.ts create mode 100644 src/apps/agents/errors/NonJsonObject.ts create mode 100644 src/apps/agents/errors/UntrustedClientError.ts create mode 100644 src/apps/agents/expressPresenter/Config.ts create mode 100644 src/apps/agents/expressPresenter/deleteProfile.ts create mode 100644 src/apps/agents/expressPresenter/getFullAgent.ts create mode 100644 src/apps/agents/expressPresenter/getProfiles.ts create mode 100644 src/apps/agents/expressPresenter/index.ts create mode 100644 src/apps/agents/expressPresenter/postProfile.ts create mode 100644 src/apps/agents/expressPresenter/putProfile.ts create mode 100644 src/apps/agents/expressPresenter/tests/alternateRequest/alternateRequest.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/alternateRequest.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/deleteExistingProfile.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/deleteNonExistingProfile.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideOrg.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideStore.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithEtags.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithScopes.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/utils/assertOutsideClient.ts create mode 100644 src/apps/agents/expressPresenter/tests/deleteProfile/utils/deleteProfile.ts create mode 100644 src/apps/agents/expressPresenter/tests/getFullAgent/getNonExistingModel.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getFullAgent/getWithScopes.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getFullAgent/utils/getFullAgent.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/alternateRequest.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/getExistingProfile.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/getNonExistingProfile.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/getOutsideOrg.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/getOutsideStore.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/getWithScopes.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/utils/assertOutsideClient.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfile/utils/getProfile.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/alternateRequest.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/getExistingProfiles.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/getNonExistingProfiles.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/getOutsideOrg.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/getOutsideStore.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/getWithScopes.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/getWithSince.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/utils/assertOutsideClient.ts create mode 100644 src/apps/agents/expressPresenter/tests/getProfiles/utils/getProfiles.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/alternateRequest.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/contentType.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/patchExistingJson.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/patchExistingObject.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/patchExistingText.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/patchNewContent.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/patchOutsideClient.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/patchWithEtags.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/patchWithScopes.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/utils/patchContent.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/utils/patchExistingProfile.ts create mode 100644 src/apps/agents/expressPresenter/tests/postProfile/utils/patchProfile.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/alternateRequest.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/contentType.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/overwriteExistingProfile.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/overwriteNonExistingProfile.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/overwriteOutsideClient.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/overwriteWithEtags.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/overwriteWithScopes.test.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteExistingProfile.ts create mode 100644 src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteProfile.ts create mode 100644 src/apps/agents/expressPresenter/tests/utils/setup.ts create mode 100644 src/apps/agents/expressPresenter/tests/utils/supertest.ts create mode 100644 src/apps/agents/expressPresenter/utils/alternateProfileRequest.ts create mode 100644 src/apps/agents/expressPresenter/utils/catchErrors.ts create mode 100644 src/apps/agents/expressPresenter/utils/contentTypePatterns.ts create mode 100644 src/apps/agents/expressPresenter/utils/deleteProfileWithService.ts create mode 100644 src/apps/agents/expressPresenter/utils/getAgent.ts create mode 100644 src/apps/agents/expressPresenter/utils/getClient.ts create mode 100644 src/apps/agents/expressPresenter/utils/getContentType.ts create mode 100644 src/apps/agents/expressPresenter/utils/getEtag.ts create mode 100644 src/apps/agents/expressPresenter/utils/getHeader.ts create mode 100644 src/apps/agents/expressPresenter/utils/getProfileFromService.ts create mode 100644 src/apps/agents/expressPresenter/utils/getProfileId.ts create mode 100644 src/apps/agents/expressPresenter/utils/getProfilesFromService.ts create mode 100644 src/apps/agents/expressPresenter/utils/getWithService.ts create mode 100644 src/apps/agents/expressPresenter/utils/handleError.ts create mode 100644 src/apps/agents/expressPresenter/utils/overwriteProfileWithService.ts create mode 100644 src/apps/agents/expressPresenter/utils/patchProfileWithService.ts create mode 100644 src/apps/agents/expressPresenter/utils/translateWarning.ts create mode 100644 src/apps/agents/expressPresenter/utils/validateVersionHeader.ts create mode 100644 src/apps/agents/fetchAuthRepo/Config.ts create mode 100644 src/apps/agents/fetchAuthRepo/getClient.ts create mode 100644 src/apps/agents/fetchAuthRepo/index.ts create mode 100644 src/apps/agents/googleStorageRepo/Config.ts create mode 100644 src/apps/agents/googleStorageRepo/clearRepo.ts create mode 100644 src/apps/agents/googleStorageRepo/deleteProfileContent.ts create mode 100644 src/apps/agents/googleStorageRepo/getProfileContent.ts create mode 100644 src/apps/agents/googleStorageRepo/index.ts create mode 100644 src/apps/agents/googleStorageRepo/storeProfileContent.ts create mode 100644 src/apps/agents/localStorageRepo/Config.ts create mode 100644 src/apps/agents/localStorageRepo/deleteProfileContent.ts create mode 100644 src/apps/agents/localStorageRepo/getProfileContent.ts create mode 100644 src/apps/agents/localStorageRepo/index.ts create mode 100644 src/apps/agents/localStorageRepo/storeProfileContent.ts create mode 100644 src/apps/agents/memoryModelsRepo/Config.ts create mode 100644 src/apps/agents/memoryModelsRepo/deleteProfile.ts create mode 100644 src/apps/agents/memoryModelsRepo/getProfile.ts create mode 100644 src/apps/agents/memoryModelsRepo/getProfiles.ts create mode 100644 src/apps/agents/memoryModelsRepo/hasProfile.ts create mode 100644 src/apps/agents/memoryModelsRepo/index.ts create mode 100644 src/apps/agents/memoryModelsRepo/overwriteProfile.ts create mode 100644 src/apps/agents/memoryModelsRepo/patchProfile.ts create mode 100644 src/apps/agents/memoryModelsRepo/utils/checkEtag.ts create mode 100644 src/apps/agents/memoryModelsRepo/utils/checkMaxEtags.ts create mode 100644 src/apps/agents/memoryModelsRepo/utils/createProfile.ts create mode 100644 src/apps/agents/memoryModelsRepo/utils/isMatchingAgent.ts create mode 100644 src/apps/agents/memoryModelsRepo/utils/matchProfileIdentifier.ts create mode 100644 src/apps/agents/memoryModelsRepo/utils/matchUniqueProfile.ts create mode 100644 src/apps/agents/models/Account.ts create mode 100644 src/apps/agents/models/Agent.ts create mode 100644 src/apps/agents/models/ClientModel.ts create mode 100644 src/apps/agents/models/Ifi.ts create mode 100644 src/apps/agents/models/Profile.ts create mode 100644 src/apps/agents/mongoAuthRepo/Config.ts create mode 100644 src/apps/agents/mongoAuthRepo/getClient.ts create mode 100644 src/apps/agents/mongoAuthRepo/index.ts create mode 100644 src/apps/agents/mongoAuthRepo/tests/getClient.test.ts create mode 100644 src/apps/agents/mongoModelsRepo/Config.ts create mode 100644 src/apps/agents/mongoModelsRepo/deleteProfile.ts create mode 100644 src/apps/agents/mongoModelsRepo/getProfile.ts create mode 100644 src/apps/agents/mongoModelsRepo/getProfiles.ts create mode 100644 src/apps/agents/mongoModelsRepo/hasProfile.ts create mode 100644 src/apps/agents/mongoModelsRepo/index.ts create mode 100644 src/apps/agents/mongoModelsRepo/overwriteProfile.ts create mode 100644 src/apps/agents/mongoModelsRepo/patchProfile.ts create mode 100644 src/apps/agents/mongoModelsRepo/utils/constants.ts create mode 100644 src/apps/agents/mongoModelsRepo/utils/getAgentFilter.ts create mode 100644 src/apps/agents/mongoModelsRepo/utils/getEtagFilter.ts create mode 100644 src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts create mode 100644 src/apps/agents/mongoModelsRepo/utils/getProfilesFilter.ts create mode 100644 src/apps/agents/mongoModelsRepo/utils/getSinceFilter.ts create mode 100644 src/apps/agents/repoFactory/AuthRepo.ts create mode 100644 src/apps/agents/repoFactory/ModelsRepo.ts create mode 100644 src/apps/agents/repoFactory/Repo.ts create mode 100644 src/apps/agents/repoFactory/StorageRepo.ts create mode 100644 src/apps/agents/repoFactory/index.ts create mode 100644 src/apps/agents/repoFactory/options/DeleteProfileContentOptions.ts create mode 100644 src/apps/agents/repoFactory/options/DeleteProfileOptions.ts create mode 100644 src/apps/agents/repoFactory/options/GetClientOptions.ts create mode 100644 src/apps/agents/repoFactory/options/GetProfileContentOptions.ts create mode 100644 src/apps/agents/repoFactory/options/GetProfileOptions.ts create mode 100644 src/apps/agents/repoFactory/options/GetProfilesOptions.ts create mode 100644 src/apps/agents/repoFactory/options/HasProfileOptions.ts create mode 100644 src/apps/agents/repoFactory/options/OverwriteProfileOptions.ts create mode 100644 src/apps/agents/repoFactory/options/PatchProfileOptions.ts create mode 100644 src/apps/agents/repoFactory/options/StoreProfileContentOptions.ts create mode 100644 src/apps/agents/repoFactory/results/DeleteProfileResult.ts create mode 100644 src/apps/agents/repoFactory/results/GetClientResult.ts create mode 100644 src/apps/agents/repoFactory/results/GetProfileContentResult.ts create mode 100644 src/apps/agents/repoFactory/results/GetProfileResult.ts create mode 100644 src/apps/agents/repoFactory/results/GetProfilesResult.ts create mode 100644 src/apps/agents/repoFactory/results/HasProfileResult.ts create mode 100644 src/apps/agents/repoFactory/results/OverwriteProfileResult.ts create mode 100644 src/apps/agents/s3StorageRepo/Config.ts create mode 100644 src/apps/agents/s3StorageRepo/deleteProfileContent.ts create mode 100644 src/apps/agents/s3StorageRepo/getProfileContent.ts create mode 100644 src/apps/agents/s3StorageRepo/index.ts create mode 100644 src/apps/agents/s3StorageRepo/readme.md create mode 100644 src/apps/agents/s3StorageRepo/storeProfileContent.ts create mode 100644 src/apps/agents/service/Config.ts create mode 100644 src/apps/agents/service/deleteProfile.ts create mode 100644 src/apps/agents/service/getClient.ts create mode 100644 src/apps/agents/service/getFullAgent.ts create mode 100644 src/apps/agents/service/getProfile.ts create mode 100644 src/apps/agents/service/getProfiles.ts create mode 100644 src/apps/agents/service/index.ts create mode 100644 src/apps/agents/service/overwriteProfile.ts create mode 100644 src/apps/agents/service/patchProfile.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/deleteExistingProfile.test.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/deleteNonExistingProfile.test.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/deleteOutsideOrg.test.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/deleteOutsideStore.test.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/deleteWithEtags.test.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/deleteWithScopes.test.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/utils/assertOutsideClient.ts create mode 100644 src/apps/agents/service/tests/deleteProfile/utils/deleteProfile.ts create mode 100644 src/apps/agents/service/tests/getClient/getNonExistingClient.test.ts create mode 100644 src/apps/agents/service/tests/getFullAgent/getNonExistingModel.test.ts create mode 100644 src/apps/agents/service/tests/getFullAgent/getWithScopes.test.ts create mode 100644 src/apps/agents/service/tests/getFullAgent/utils/getFullAgent.ts create mode 100644 src/apps/agents/service/tests/getProfile/getExistingProfile.test.ts create mode 100644 src/apps/agents/service/tests/getProfile/getNonExistingProfile.test.ts create mode 100644 src/apps/agents/service/tests/getProfile/getOutsideOrg.test.ts create mode 100644 src/apps/agents/service/tests/getProfile/getOutsideStore.test.ts create mode 100644 src/apps/agents/service/tests/getProfile/getWithScopes.test.ts create mode 100644 src/apps/agents/service/tests/getProfile/utils/assertOutsideClient.ts create mode 100644 src/apps/agents/service/tests/getProfiles/getExistingProfiles.test.ts create mode 100644 src/apps/agents/service/tests/getProfiles/getNonExistingProfiles.test.ts create mode 100644 src/apps/agents/service/tests/getProfiles/getOutsideOrg.test.ts create mode 100644 src/apps/agents/service/tests/getProfiles/getOutsideStore.test.ts create mode 100644 src/apps/agents/service/tests/getProfiles/getWithScopes.test.ts create mode 100644 src/apps/agents/service/tests/getProfiles/getWithSince.test.ts create mode 100644 src/apps/agents/service/tests/getProfiles/utils/assertOutsideClient.ts create mode 100644 src/apps/agents/service/tests/overwriteProfile/overwriteExistingProfile.ts create mode 100644 src/apps/agents/service/tests/overwriteProfile/overwriteNonExistingProfile.test.ts create mode 100644 src/apps/agents/service/tests/overwriteProfile/overwriteOutsideClient.test.ts create mode 100644 src/apps/agents/service/tests/overwriteProfile/overwriteWithEtags.test.ts create mode 100644 src/apps/agents/service/tests/overwriteProfile/overwriteWithScopes.test.ts create mode 100644 src/apps/agents/service/tests/overwriteProfile/utils/overwriteProfile.ts create mode 100644 src/apps/agents/service/tests/patchProfile/patchExistingJson.test.ts create mode 100644 src/apps/agents/service/tests/patchProfile/patchExistingObject.test.ts create mode 100644 src/apps/agents/service/tests/patchProfile/patchExistingText.test.ts create mode 100644 src/apps/agents/service/tests/patchProfile/patchNewContent.test.ts create mode 100644 src/apps/agents/service/tests/patchProfile/patchOutsideClient.test.ts create mode 100644 src/apps/agents/service/tests/patchProfile/patchWithEtags.test.ts create mode 100644 src/apps/agents/service/tests/patchProfile/patchWithScopes.test.ts create mode 100644 src/apps/agents/service/tests/patchProfile/utils/patchContent.ts create mode 100644 src/apps/agents/service/tests/patchProfile/utils/patchProfile.ts create mode 100644 src/apps/agents/service/tests/utils/setup.ts create mode 100644 src/apps/agents/service/tests/validateAgent/validateAgent.test.ts create mode 100644 src/apps/agents/service/utils/checkProfileReadScopes.ts create mode 100644 src/apps/agents/service/utils/checkProfileWriteScopes.ts create mode 100644 src/apps/agents/service/utils/createEtag.ts create mode 100644 src/apps/agents/service/utils/validateAgent.ts create mode 100644 src/apps/agents/service/utils/validateSince.ts create mode 100644 src/apps/agents/serviceFactory/Service.ts create mode 100644 src/apps/agents/serviceFactory/index.ts create mode 100644 src/apps/agents/serviceFactory/options/DeleteProfileOptions.ts create mode 100644 src/apps/agents/serviceFactory/options/GetClientOptions.ts create mode 100644 src/apps/agents/serviceFactory/options/GetFullAgentOptions.ts create mode 100644 src/apps/agents/serviceFactory/options/GetProfileOptions.ts create mode 100644 src/apps/agents/serviceFactory/options/GetProfilesOptions.ts create mode 100644 src/apps/agents/serviceFactory/options/OverwriteProfileOptions.ts create mode 100644 src/apps/agents/serviceFactory/options/PatchProfileOptions.ts create mode 100644 src/apps/agents/serviceFactory/results/GetClientResult.ts create mode 100644 src/apps/agents/serviceFactory/results/GetFullAgentResult.ts create mode 100644 src/apps/agents/serviceFactory/results/GetProfileResult.ts create mode 100644 src/apps/agents/serviceFactory/results/GetProfilesResult.ts create mode 100644 src/apps/agents/testAuthRepo/Config.ts create mode 100644 src/apps/agents/testAuthRepo/getClient.ts create mode 100644 src/apps/agents/testAuthRepo/index.ts create mode 100644 src/apps/agents/tests/getFileExtension.test.ts create mode 100644 src/apps/agents/translatorFactory/Translator.ts create mode 100644 src/apps/agents/translatorFactory/en.ts create mode 100644 src/apps/agents/translatorFactory/index.ts create mode 100644 src/apps/agents/utils/assertDeleted.ts create mode 100644 src/apps/agents/utils/assertImmutableProfile.ts create mode 100644 src/apps/agents/utils/assertProfile.ts create mode 100644 src/apps/agents/utils/constants.ts create mode 100644 src/apps/agents/utils/createImmutableProfile.ts create mode 100644 src/apps/agents/utils/createJsonProfile.ts create mode 100644 src/apps/agents/utils/createObjectProfile.ts create mode 100644 src/apps/agents/utils/createTextProfile.ts create mode 100644 src/apps/agents/utils/getFileExtension.ts create mode 100644 src/apps/agents/utils/getStorageDir.ts create mode 100644 src/apps/agents/utils/getTestProfile.ts create mode 100644 src/apps/agents/utils/getTestProfiles.ts create mode 100644 src/apps/agents/utils/overwriteProfileOutsideClient.ts create mode 100644 src/apps/agents/utils/parseJson.ts create mode 100644 src/apps/agents/utils/patchProfileOutsideClient.ts create mode 100644 src/apps/agents/utils/scopes.ts create mode 100644 src/apps/agents/utils/testService.ts create mode 100644 src/apps/agents/utils/testValues.ts diff --git a/package.json b/package.json index 65708af8a..817ee5901 100644 --- a/package.json +++ b/package.json @@ -38,22 +38,36 @@ "npm": ">3.0.0" }, "dependencies": { - "@learninglocker/xapi-agents": "4.4.3", + "@azure/storage-blob": "10.3.0", + "@google-cloud/storage": "1.5.2", + "@learninglocker/xapi-validation": "2.1.10", "accept-language-parser": "^1.5.0", + "atob": "2.0.3", + "aws-sdk": "2.205.0", + "bluebird": "3.5.0", "boolean": "^0.2.0", + "btoa": "1.1.2", "dotenv": "^5.0.0", "express": "^4.14.1", + "fs-extra": "5.0.0", + "http-status-codes": "1.3.0", "install": "^0.13.0", "ioredis": "^4.14.0", "jscommons": "^2.3.3", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.4", + "mime-types": "2.1.17", "mongodb": "^3.0.2", + "node-fetch": "2.0.0", "object-hash": "^1.3.1", "query-string": "^6.8.2", "redis": "^2.8.0", + "rulr": "4.0.1", + "sha1": "1.1.1", "source-map-support": "^0.5.0", - "string-to-stream": "^2.0.0" + "stream-to-string": "^1.2.0", + "string-to-stream": "^2.0.0", + "uuid": "3.0.1" }, "devDependencies": { "@ht2-labs/semantic-release": "1.1.90", @@ -72,6 +86,7 @@ "@types/mocha": "5.2.7", "@types/mongodb": "3.1.10", "@types/node": "9.6.51", + "@types/node-fetch": "2.5.0", "@types/object-hash": "1.3.0", "@types/redis": "2.8.6", "@types/source-map-support": "0.5.0", diff --git a/src/apps/activities/repo/storage/factory.ts b/src/apps/activities/repo/storage/factory.ts index f38dcd9e0..5087140b5 100644 --- a/src/apps/activities/repo/storage/factory.ts +++ b/src/apps/activities/repo/storage/factory.ts @@ -5,7 +5,7 @@ import { StorageURL, } from '@azure/storage-blob'; import Storage from '@google-cloud/storage'; -import { S3 } from 'aws-sdk'; +import S3 from 'aws-sdk/clients/s3'; import azureStorageRepo from '../../azureStorageRepo'; import googleStorageRepo from '../../googleStorageRepo'; import localStorageRepo from '../../localStorageRepo'; @@ -18,7 +18,7 @@ export default (factoryConfig: FactoryConfig): Repo => { case 's3': return s3StorageRepo({ bucketName: factoryConfig.s3.bucketName, - client: new S3(factoryConfig.s3.awsConfig), + client: new S3(factoryConfig.s3.awsConfig) as any, subFolder: factoryConfig.s3.subFolder, }); case 'google': diff --git a/src/apps/activities/repoFactory/index.ts b/src/apps/activities/repoFactory/index.ts index beb21f98c..b0a7b2436 100644 --- a/src/apps/activities/repoFactory/index.ts +++ b/src/apps/activities/repoFactory/index.ts @@ -68,7 +68,7 @@ const getStorageRepo = (): StorageRepo => { case 's3': return s3StorageRepo({ bucketName: config.s3StorageRepo.bucketName, - client: new S3(config.s3StorageRepo.awsConfig), + client: new S3(config.s3StorageRepo.awsConfig) as any, subFolder: config.storageSubFolders.activities, }); case 'google': diff --git a/src/apps/agents/app.ts b/src/apps/agents/app.ts index a07085fdd..dd58c4f39 100644 --- a/src/apps/agents/app.ts +++ b/src/apps/agents/app.ts @@ -1,10 +1,10 @@ /* tslint:disable:max-file-line-count */ -import presenterFactory from '@learninglocker/xapi-agents/dist/expressPresenter'; -import serviceFactory from '@learninglocker/xapi-agents/dist/service'; -import enTranslator from '@learninglocker/xapi-agents/dist/translatorFactory/en'; import { Router } from 'express'; import AppConfig from './AppConfig'; +import presenterFactory from './expressPresenter'; import repoFactory from './repo/factory'; +import serviceFactory from './service'; +import enTranslator from './translatorFactory/en'; export default (appConfig: AppConfig): Router => { const translator = enTranslator; diff --git a/src/apps/agents/azureStorageRepo/Config.ts b/src/apps/agents/azureStorageRepo/Config.ts new file mode 100644 index 000000000..d2058de4a --- /dev/null +++ b/src/apps/agents/azureStorageRepo/Config.ts @@ -0,0 +1,6 @@ +import { ContainerURL } from '@azure/storage-blob'; + +export default interface Config { + readonly containerUrl: ContainerURL; + readonly subFolder: string; +} diff --git a/src/apps/agents/azureStorageRepo/clearRepo.ts b/src/apps/agents/azureStorageRepo/clearRepo.ts new file mode 100644 index 000000000..f6ed793ed --- /dev/null +++ b/src/apps/agents/azureStorageRepo/clearRepo.ts @@ -0,0 +1,27 @@ +import { + Aborter, + BlobURL, + Models, +} from '@azure/storage-blob'; +import Config from './Config'; + +export default (config: Config) => { + return async (): Promise => { + // tslint:disable-next-line:no-let + let marker; + do { + const listBlobsResponse: Models.ContainerListBlobFlatSegmentResponse = + await config.containerUrl.listBlobFlatSegment(Aborter.none, marker); + marker = listBlobsResponse.nextMarker; + const deletePromises = listBlobsResponse.segment.blobItems.map( + async (blobItem: Models.BlobItem) => { + const blobUrl = BlobURL.fromContainerURL(config.containerUrl, blobItem.name); + if (blobItem.name.startsWith(config.subFolder)) { + await blobUrl.delete(Aborter.none); + } + }, + ); + await Promise.all(deletePromises); + } while (marker !== ''); + }; +}; diff --git a/src/apps/agents/azureStorageRepo/deleteProfileContent.ts b/src/apps/agents/azureStorageRepo/deleteProfileContent.ts new file mode 100644 index 000000000..cfb0aebf0 --- /dev/null +++ b/src/apps/agents/azureStorageRepo/deleteProfileContent.ts @@ -0,0 +1,18 @@ +import { + Aborter, + BlobURL, +} from '@azure/storage-blob'; +import DeleteProfileContentOptions from '../repoFactory/options/DeleteProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + + export default (config: Config) => { + return async (opts: DeleteProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + + const blobUrl = BlobURL.fromContainerURL(config.containerUrl, filePath); + + await blobUrl.delete(Aborter.none); + }; +}; diff --git a/src/apps/agents/azureStorageRepo/getProfileContent.ts b/src/apps/agents/azureStorageRepo/getProfileContent.ts new file mode 100644 index 000000000..d5e00f2ee --- /dev/null +++ b/src/apps/agents/azureStorageRepo/getProfileContent.ts @@ -0,0 +1,20 @@ +import { Aborter, BlobURL } from '@azure/storage-blob'; +import GetProfileContentOptions from '../repoFactory/options/GetProfileContentOptions'; +import GetProfileContentResult from '../repoFactory/results/GetProfileContentResult'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + + export default (config: Config) => { + return async (opts: GetProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + + const blobUrl = BlobURL.fromContainerURL(config.containerUrl, filePath); + const content = (await blobUrl.download(Aborter.none, 0)).readableStreamBody; + if (content === undefined) { + throw new Error('Blob not found'); + } + + return { content }; + }; +}; diff --git a/src/apps/agents/azureStorageRepo/index.ts b/src/apps/agents/azureStorageRepo/index.ts new file mode 100644 index 000000000..d68080d38 --- /dev/null +++ b/src/apps/agents/azureStorageRepo/index.ts @@ -0,0 +1,17 @@ +import StorageRepo from '../repoFactory/StorageRepo'; +import clearRepo from './clearRepo'; +import Config from './Config'; +import deleteProfileContent from './deleteProfileContent'; +import getProfileContent from './getProfileContent'; +import storeProfileContent from './storeProfileContent'; + + export default (config: Config): StorageRepo => { + return { + clearRepo: clearRepo(config), + deleteProfileContent: deleteProfileContent(config), + getProfileContent: getProfileContent(config), + migrate: async () => Promise.resolve(), + rollback: async () => Promise.resolve(), + storeProfileContent: storeProfileContent(config), + }; +}; diff --git a/src/apps/agents/azureStorageRepo/storeProfileContent.ts b/src/apps/agents/azureStorageRepo/storeProfileContent.ts new file mode 100644 index 000000000..32ac762d0 --- /dev/null +++ b/src/apps/agents/azureStorageRepo/storeProfileContent.ts @@ -0,0 +1,48 @@ +import { + Aborter, + BlobURL, + BlockBlobURL, + uploadStreamToBlockBlob, +} from '@azure/storage-blob'; +import { Readable } from 'stream'; +import StoreProfileContentOptions from '../repoFactory/options/StoreProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +const BYTES_IN_KILOBYTES = 1024; +const KILOBYTES_IN_MEGABYTES = 1024; +const FOUR = 4; + +// https://github.com/Azure/azure-storage-js/blob/master/blob/samples/highlevel.sample.js +const BUFFER_SIZE = FOUR * KILOBYTES_IN_MEGABYTES * BYTES_IN_KILOBYTES; // 4MB +const MAX_BUFFERS = 20; + +export default (config: Config) => { + return async (opts: StoreProfileContentOptions): Promise => { + return new Promise(async (resolve, reject) => { + const profileDir = getStorageDir({ + subfolder: config.subFolder, + lrs_id: opts.lrs_id, + }); + const filePath = `${profileDir}/${opts.key}`; + + const blobUrl = BlobURL.fromContainerURL(config.containerUrl, filePath); + const blockBlobUrl = BlockBlobURL.fromBlobURL(blobUrl); + + opts.content.on('error', reject); + + try { + await uploadStreamToBlockBlob( + Aborter.none, + opts.content as Readable, + blockBlobUrl, + BUFFER_SIZE, + MAX_BUFFERS, + ); + } catch (err) { + reject(err); + } + resolve(); + }); + }; +}; diff --git a/src/apps/agents/errors/Conflict.ts b/src/apps/agents/errors/Conflict.ts new file mode 100644 index 000000000..ff8f0b69a --- /dev/null +++ b/src/apps/agents/errors/Conflict.ts @@ -0,0 +1,4 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError {} diff --git a/src/apps/agents/errors/ExpiredClientError.ts b/src/apps/agents/errors/ExpiredClientError.ts new file mode 100644 index 000000000..67de6abe2 --- /dev/null +++ b/src/apps/agents/errors/ExpiredClientError.ts @@ -0,0 +1,8 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError { + constructor() { + super(); + } +} diff --git a/src/apps/agents/errors/IfMatch.ts b/src/apps/agents/errors/IfMatch.ts new file mode 100644 index 000000000..ff8f0b69a --- /dev/null +++ b/src/apps/agents/errors/IfMatch.ts @@ -0,0 +1,4 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError {} diff --git a/src/apps/agents/errors/IfNoneMatch.ts b/src/apps/agents/errors/IfNoneMatch.ts new file mode 100644 index 000000000..ff8f0b69a --- /dev/null +++ b/src/apps/agents/errors/IfNoneMatch.ts @@ -0,0 +1,4 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError {} diff --git a/src/apps/agents/errors/InvalidMethod.ts b/src/apps/agents/errors/InvalidMethod.ts new file mode 100644 index 000000000..3094691ff --- /dev/null +++ b/src/apps/agents/errors/InvalidMethod.ts @@ -0,0 +1,8 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError { + constructor(public method: string) { + super(); + } +} diff --git a/src/apps/agents/errors/JsonSyntaxError.ts b/src/apps/agents/errors/JsonSyntaxError.ts new file mode 100644 index 000000000..f9372d121 --- /dev/null +++ b/src/apps/agents/errors/JsonSyntaxError.ts @@ -0,0 +1,8 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError { + constructor(public path: string[]) { + super(); + } +} diff --git a/src/apps/agents/errors/MaxEtags.ts b/src/apps/agents/errors/MaxEtags.ts new file mode 100644 index 000000000..ff8f0b69a --- /dev/null +++ b/src/apps/agents/errors/MaxEtags.ts @@ -0,0 +1,4 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError {} diff --git a/src/apps/agents/errors/MissingEtags.ts b/src/apps/agents/errors/MissingEtags.ts new file mode 100644 index 000000000..ff8f0b69a --- /dev/null +++ b/src/apps/agents/errors/MissingEtags.ts @@ -0,0 +1,4 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError {} diff --git a/src/apps/agents/errors/NonJsonObject.ts b/src/apps/agents/errors/NonJsonObject.ts new file mode 100644 index 000000000..ff8f0b69a --- /dev/null +++ b/src/apps/agents/errors/NonJsonObject.ts @@ -0,0 +1,4 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError {} diff --git a/src/apps/agents/errors/UntrustedClientError.ts b/src/apps/agents/errors/UntrustedClientError.ts new file mode 100644 index 000000000..67de6abe2 --- /dev/null +++ b/src/apps/agents/errors/UntrustedClientError.ts @@ -0,0 +1,8 @@ +/* tslint:disable:no-class */ +import BaseError from 'jscommons/dist/errors/BaseError'; + +export default class extends BaseError { + constructor() { + super(); + } +} diff --git a/src/apps/agents/expressPresenter/Config.ts b/src/apps/agents/expressPresenter/Config.ts new file mode 100644 index 000000000..648b6072e --- /dev/null +++ b/src/apps/agents/expressPresenter/Config.ts @@ -0,0 +1,12 @@ +import CommonExpressConfig from 'jscommons/dist/expressPresenter/Config'; +import Tracker from 'jscommons/dist/tracker/Tracker'; +import Service from '../serviceFactory/Service'; +import Translator from '../translatorFactory/Translator'; + +interface Config extends CommonExpressConfig { + readonly service: Service; + readonly translator: Translator; + readonly tracker: Promise; +} + +export default Config; diff --git a/src/apps/agents/expressPresenter/deleteProfile.ts b/src/apps/agents/expressPresenter/deleteProfile.ts new file mode 100644 index 000000000..1a81e9578 --- /dev/null +++ b/src/apps/agents/expressPresenter/deleteProfile.ts @@ -0,0 +1,12 @@ +import { Request, Response } from 'express'; +import Config from './Config'; +import catchErrors from './utils/catchErrors'; +import deleteProfileWithService from './utils/deleteProfileWithService'; + +export default (config: Config) => { + return catchErrors(config, async (req: Request, res: Response): Promise => { + const query = req.query; + const headers = req.headers; + return deleteProfileWithService({ config, res, query, headers }); + }); +}; diff --git a/src/apps/agents/expressPresenter/getFullAgent.ts b/src/apps/agents/expressPresenter/getFullAgent.ts new file mode 100644 index 000000000..280e27c1a --- /dev/null +++ b/src/apps/agents/expressPresenter/getFullAgent.ts @@ -0,0 +1,20 @@ +import { Request, Response } from 'express'; +import { OK } from 'http-status-codes'; +import { xapiHeaderVersion } from '../utils/constants'; +import Config from './Config'; +import catchErrors from './utils/catchErrors'; +import getAgent from './utils/getAgent'; +import getClient from './utils/getClient'; +import validateVersionHeader from './utils/validateVersionHeader'; + +export default (config: Config) => { + return catchErrors(config, async (req: Request, res: Response): Promise => { + const client = await getClient(config, req.header('Authorization')); + validateVersionHeader(req.header('X-Experience-API-Version')); + const agent = getAgent(req.query.agent); + const result = await config.service.getFullAgent({ client, agent }); + res.status(OK); + res.setHeader('X-Experience-API-Version', xapiHeaderVersion); + res.json(result); + }); +}; diff --git a/src/apps/agents/expressPresenter/getProfiles.ts b/src/apps/agents/expressPresenter/getProfiles.ts new file mode 100644 index 000000000..883b34e3c --- /dev/null +++ b/src/apps/agents/expressPresenter/getProfiles.ts @@ -0,0 +1,12 @@ +import { Request, Response } from 'express'; +import Config from './Config'; +import catchErrors from './utils/catchErrors'; +import getWithService from './utils/getWithService'; + +export default (config: Config) => { + return catchErrors(config, async (req: Request, res: Response): Promise => { + const query = req.query; + const headers = req.headers; + return getWithService({ config, res, query, headers }); + }); +}; diff --git a/src/apps/agents/expressPresenter/index.ts b/src/apps/agents/expressPresenter/index.ts new file mode 100644 index 000000000..98ecaa79e --- /dev/null +++ b/src/apps/agents/expressPresenter/index.ts @@ -0,0 +1,26 @@ +import { Router } from 'express'; +import mixinCors from 'jscommons/dist/expressPresenter/mixins/cors'; +import mixinHelmet from 'jscommons/dist/expressPresenter/mixins/helmet'; +import mixinMorgan from 'jscommons/dist/expressPresenter/mixins/morgan'; +import Config from './Config'; +import deleteProfile from './deleteProfile'; +import getFullAgent from './getFullAgent'; +import getProfiles from './getProfiles'; +import postProfile from './postProfile'; +import putProfile from './putProfile'; + +export default (config: Config): Router => { + const router = Router(); + + router.use(mixinCors()); + router.use(mixinHelmet()); + router.use(mixinMorgan(config.morganDirectory)); + + router.delete('/profile', deleteProfile(config)); + router.get('/profile', getProfiles(config)); + router.put('/profile', putProfile(config)); + router.post('/profile', postProfile(config)); + router.get('', getFullAgent(config)); + + return router; +}; diff --git a/src/apps/agents/expressPresenter/postProfile.ts b/src/apps/agents/expressPresenter/postProfile.ts new file mode 100644 index 000000000..628f38dfc --- /dev/null +++ b/src/apps/agents/expressPresenter/postProfile.ts @@ -0,0 +1,20 @@ +import { Request, Response } from 'express'; +import Config from './Config'; +import alternateProfileRequest from './utils/alternateProfileRequest'; +import catchErrors from './utils/catchErrors'; +import patchProfileWithService from './utils/patchProfileWithService'; + +export default (config: Config) => { + return catchErrors(config, async (req: Request, res: Response): Promise => { + const query = req.query; + const method = query.method; + + if (method !== undefined) { + return alternateProfileRequest({ config, method, req, res }); + } + + const headers = req.headers; + const content = req; + return patchProfileWithService({ config, res, query, headers, content }); + }); +}; diff --git a/src/apps/agents/expressPresenter/putProfile.ts b/src/apps/agents/expressPresenter/putProfile.ts new file mode 100644 index 000000000..4600b6d6b --- /dev/null +++ b/src/apps/agents/expressPresenter/putProfile.ts @@ -0,0 +1,13 @@ +import { Request, Response } from 'express'; +import Config from './Config'; +import catchErrors from './utils/catchErrors'; +import overwriteProfileWithService from './utils/overwriteProfileWithService'; + +export default (config: Config) => { + return catchErrors(config, async (req: Request, res: Response): Promise => { + const query = req.query; + const headers = req.headers; + const content = req; + return overwriteProfileWithService({ config, res, query, headers, content }); + }); +}; diff --git a/src/apps/agents/expressPresenter/tests/alternateRequest/alternateRequest.test.ts b/src/apps/agents/expressPresenter/tests/alternateRequest/alternateRequest.test.ts new file mode 100644 index 000000000..802a5da49 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/alternateRequest/alternateRequest.test.ts @@ -0,0 +1,33 @@ +import { BAD_REQUEST, OK } from 'http-status-codes'; +import { stringify as createQueryString } from 'query-string'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import { ALTERNATE_CONTENT_TYPE, TEST_MBOX_AGENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('expressPresenter using the alternate request syntax', () => { + const { supertest } = setup(); + + it('should return error when using an invalid method', async () => { + await supertest + .post(`${route}/profile`) + .set('Content-Type', ALTERNATE_CONTENT_TYPE) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ method: 'invalid_method' }) + .send({ + agent: JSON.stringify(TEST_MBOX_AGENT), + }) + .expect(BAD_REQUEST); + }); + + it('should not error when using an invalid content type', async () => { + await supertest + .post(`${route}/profile`) + .set('Content-Type', 'invalid_content_type') + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ method: 'GET' }) + .send(createQueryString({ + agent: JSON.stringify(TEST_MBOX_AGENT), + })) + .expect(OK, []); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/alternateRequest.test.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/alternateRequest.test.ts new file mode 100644 index 000000000..9199724f2 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/alternateRequest.test.ts @@ -0,0 +1,29 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertDeleted from '../../../utils/assertDeleted'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + ALTERNATE_CONTENT_TYPE, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('expressPresenter.deleteProfile using the alternate request syntax', () => { + const { supertest } = setup(); + + it('should delete when deleting text', async () => { + await createTextProfile(); + await supertest + .post(`${route}/profile`) + .set('Content-Type', ALTERNATE_CONTENT_TYPE) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ method: 'DELETE' }) + .send({ + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, + }) + .expect(NO_CONTENT); + await assertDeleted(); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/deleteExistingProfile.test.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteExistingProfile.test.ts new file mode 100644 index 000000000..d29ded7d4 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteExistingProfile.test.ts @@ -0,0 +1,22 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertDeleted from '../../../utils/assertDeleted'; +import createJsonProfile from '../../../utils/createJsonProfile'; +import createTextProfile from '../../../utils/createTextProfile'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('expressPresenter.deleteProfile with existing profile', () => { + setup(); + + it('should delete when deleting text', async () => { + await createTextProfile(); + await deleteProfile().expect(NO_CONTENT); + await assertDeleted(); + }); + + it('should delete when deleting json', async () => { + await createJsonProfile(); + await deleteProfile().expect(NO_CONTENT); + await assertDeleted(); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/deleteNonExistingProfile.test.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteNonExistingProfile.test.ts new file mode 100644 index 000000000..36fd3d027 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteNonExistingProfile.test.ts @@ -0,0 +1,32 @@ +import { BAD_REQUEST, NO_CONTENT } from 'http-status-codes'; +import { TEST_INVALID_AGENT, TEST_INVALID_JSON_CONTENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('expressPresenter.deleteProfile with non-existing state', () => { + setup(); + + it('should not error when deleting', async () => { + await deleteProfile().expect(NO_CONTENT); + }); + + it('should throw warnings when using an invalid agent', async () => { + await deleteProfile({ + agent: JSON.stringify(TEST_INVALID_AGENT), + }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the profile id', async () => { + await deleteProfile({ profileId: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the agent', async () => { + await deleteProfile({ agent: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when using invalid json in agent', async () => { + await deleteProfile({ + agent: TEST_INVALID_JSON_CONTENT, + }).expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideOrg.test.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideOrg.test.ts new file mode 100644 index 000000000..323277c31 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideOrg.test.ts @@ -0,0 +1,23 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { + TEST_CLIENT_OUTSIDE_ORG, + TEST_CONTENT, + TEST_OBJECT_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('expressPresenter.deleteProfile outside the organisation', () => { + setup(); + + it('should error when deleting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_ORG, TEST_CONTENT); + }); + + it('should error when deleting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_ORG, TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideStore.test.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideStore.test.ts new file mode 100644 index 000000000..072d70c71 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteOutsideStore.test.ts @@ -0,0 +1,23 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { + TEST_CLIENT_OUTSIDE_STORE, + TEST_CONTENT, + TEST_OBJECT_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('expressPresenter.deleteProfile outside the store', () => { + setup(); + + it('should error when deleting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_STORE, TEST_CONTENT); + }); + + it('should error when deleting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_STORE, TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithEtags.test.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithEtags.test.ts new file mode 100644 index 000000000..42350bed8 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithEtags.test.ts @@ -0,0 +1,29 @@ +import { NO_CONTENT, PRECONDITION_FAILED } from 'http-status-codes'; +import createTextProfile from '../../../utils/createTextProfile'; +import getTestProfile from '../../../utils/getTestProfile'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('expressPresenter.deleteProfile with etags', () => { + setup(); + + it('should allow deletion when using a correct etag', async () => { + await createTextProfile(); + const getProfileResult = await getTestProfile(); + await deleteProfile() + .set('If-Match', `"${getProfileResult.etag}"`) + .expect(NO_CONTENT); + }); + + it('should throw precondition error when using an incorrect ifMatch', async () => { + await createTextProfile(); + await deleteProfile() + .set('If-Match', `"incorrect_etag"`) + .expect(PRECONDITION_FAILED); + }); + + it('should allow deletion when not using an IfMatch', async () => { + await createTextProfile(); + await deleteProfile().expect(NO_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithScopes.test.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithScopes.test.ts new file mode 100644 index 000000000..5c87c5b8c --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/deleteWithScopes.test.ts @@ -0,0 +1,29 @@ +import { FORBIDDEN, NO_CONTENT } from 'http-status-codes'; +import { + TEST_EXPIRED_ORG_TOKEN, + TEST_INVALID_SCOPE_TOKEN, + TEST_UNTRUSTED_TOKEN, + TEST_VALID_SCOPE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('expressPresenter.deleteProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + await deleteProfile().set('Authorization', TEST_INVALID_SCOPE_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using expired client', async () => { + await deleteProfile().set('Authorization', TEST_EXPIRED_ORG_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using untrusted client', async () => { + await deleteProfile().set('Authorization', TEST_UNTRUSTED_TOKEN).expect(FORBIDDEN); + }); + + it('should not error when using valid scopes', async () => { + await deleteProfile().set('Authorization', TEST_VALID_SCOPE_TOKEN).expect(NO_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/utils/assertOutsideClient.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/utils/assertOutsideClient.ts new file mode 100644 index 000000000..4340861a2 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/utils/assertOutsideClient.ts @@ -0,0 +1,9 @@ +import { NO_CONTENT } from 'http-status-codes'; +import ClientModel from '../../../../models/ClientModel'; +import assertProfile from '../../../../utils/assertProfile'; +import deleteProfile from './deleteProfile'; + +export default async (client: ClientModel, content: string) => { + await deleteProfile().expect(NO_CONTENT); + await assertProfile(content, { client }); +}; diff --git a/src/apps/agents/expressPresenter/tests/deleteProfile/utils/deleteProfile.ts b/src/apps/agents/expressPresenter/tests/deleteProfile/utils/deleteProfile.ts new file mode 100644 index 000000000..ed7767d7e --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/deleteProfile/utils/deleteProfile.ts @@ -0,0 +1,19 @@ +import { Test } from 'supertest'; +import { route, xapiHeaderVersion } from '../../../../utils/constants'; +import { TEST_MBOX_AGENT, TEST_PROFILE_ID } from '../../../../utils/testValues'; +import supertest from '../../utils/supertest'; + +const options = { + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, +}; + +export default (optsOverrides: object = {}): Test => { + return supertest + .delete(`${route}/profile`) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + ...options, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/expressPresenter/tests/getFullAgent/getNonExistingModel.test.ts b/src/apps/agents/expressPresenter/tests/getFullAgent/getNonExistingModel.test.ts new file mode 100644 index 000000000..ba028b569 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getFullAgent/getNonExistingModel.test.ts @@ -0,0 +1,69 @@ +import { BAD_REQUEST, OK } from 'http-status-codes'; +import Account from '../../../models/Account'; +import GetFullAgentResult from '../../../serviceFactory/results/GetFullAgentResult'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import { + TEST_ACCOUNT_AGENT, + TEST_INVALID_AGENT, + TEST_INVALID_JSON_CONTENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getFullAgent from './utils/getFullAgent'; + +describe('expressPresenter.getFullAgent with non-existing model', () => { + const { supertest } = setup(); + + const assertFullAgent = async (agent: any, resultOverrides: Partial) => { + const expectedResult: GetFullAgentResult = { + account: [], + mbox: [], + mbox_sha1sum: [], + name: [], + objectType: 'Person', + openid: [], + ...resultOverrides, + }; + await getFullAgent(agent).expect(OK, expectedResult); + }; + + it('should return the agent when using mbox', async () => { + await assertFullAgent(TEST_MBOX_AGENT, { + mbox: [TEST_MBOX_AGENT.mbox as string], + }); + }); + + it('should return the agent when using mbox_sha1sum', async () => { + await assertFullAgent(TEST_MBOXSHA1_AGENT, { + mbox_sha1sum: [TEST_MBOXSHA1_AGENT.mbox_sha1sum as string], + }); + }); + + it('should return the agent when using openid', async () => { + await assertFullAgent(TEST_OPENID_AGENT, { + openid: [TEST_OPENID_AGENT.openid as string], + }); + }); + + it('should return the agent when using account', async () => { + await assertFullAgent(TEST_ACCOUNT_AGENT, { + account: [TEST_ACCOUNT_AGENT.account as Account], + }); + }); + + it('should throw warnings when using an invalid agent', async () => { + await getFullAgent(TEST_INVALID_AGENT).expect(BAD_REQUEST); + }); + + it('should throw warnings when using invalid json in agent', async () => { + await supertest + .get(route) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + agent: TEST_INVALID_JSON_CONTENT, + }) + .expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getFullAgent/getWithScopes.test.ts b/src/apps/agents/expressPresenter/tests/getFullAgent/getWithScopes.test.ts new file mode 100644 index 000000000..2e443eb9f --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getFullAgent/getWithScopes.test.ts @@ -0,0 +1,29 @@ +import { FORBIDDEN, OK } from 'http-status-codes'; +import { + TEST_EXPIRED_ORG_TOKEN, + TEST_INVALID_SCOPE_TOKEN, + TEST_UNTRUSTED_TOKEN, + TEST_VALID_SCOPE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getFullAgent from './utils/getFullAgent'; + +describe('expressPresenter.getFullAgent with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + await getFullAgent().set('Authorization', TEST_INVALID_SCOPE_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using expired client', async () => { + await getFullAgent().set('Authorization', TEST_EXPIRED_ORG_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using untrusted client', async () => { + await getFullAgent().set('Authorization', TEST_UNTRUSTED_TOKEN).expect(FORBIDDEN); + }); + + it('should not throw error when using valid scopes', async () => { + await getFullAgent().set('Authorization', TEST_VALID_SCOPE_TOKEN).expect(OK); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getFullAgent/utils/getFullAgent.ts b/src/apps/agents/expressPresenter/tests/getFullAgent/utils/getFullAgent.ts new file mode 100644 index 000000000..6b609ac70 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getFullAgent/utils/getFullAgent.ts @@ -0,0 +1,16 @@ +import { Test } from 'supertest'; +import Agent from '../../../../models/Agent'; +import { route, xapiHeaderVersion } from '../../../../utils/constants'; +import { + TEST_MBOX_AGENT, +} from '../../../../utils/testValues'; +import supertest from '../../utils/supertest'; + +export default (agent: Agent = TEST_MBOX_AGENT): Test => { + return supertest + .get(route) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + agent: JSON.stringify(agent), + }); +}; diff --git a/src/apps/agents/expressPresenter/tests/getProfile/alternateRequest.test.ts b/src/apps/agents/expressPresenter/tests/getProfile/alternateRequest.test.ts new file mode 100644 index 000000000..a68a8e71a --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/alternateRequest.test.ts @@ -0,0 +1,28 @@ +import { OK } from 'http-status-codes'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + ALTERNATE_CONTENT_TYPE, + TEST_CONTENT, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('expressPresenter.getProfile using the alternate request syntax', () => { + const { supertest } = setup(); + + it('should get when getting text', async () => { + await createTextProfile(); + await supertest + .post(`${route}/profile`) + .set('Content-Type', ALTERNATE_CONTENT_TYPE) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ method: 'GET' }) + .send({ + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, + }) + .expect(OK, TEST_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfile/getExistingProfile.test.ts b/src/apps/agents/expressPresenter/tests/getProfile/getExistingProfile.test.ts new file mode 100644 index 000000000..a2ffa8525 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/getExistingProfile.test.ts @@ -0,0 +1,72 @@ +import { OK } from 'http-status-codes'; +import createJsonProfile from '../../../utils/createJsonProfile'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + TEST_ACCOUNT_AGENT, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getProfile from './utils/getProfile'; + +describe('expressPresenter.getProfile with existing state', () => { + setup(); + + it('should get when getting text', async () => { + await createTextProfile(); + await getProfile().expect(OK, TEST_CONTENT); + }); + + it('should get when agent properties are in a different order', async () => { + // tslint:disable:object-literal-sort-keys + const creationAgent = { + objectType: 'Agent', + account: { + name: 'steely.eyed', + homePage: 'http://missile.man', + }, + }; + const retrievalAgent = JSON.stringify({ + objectType: 'Agent', + account: { + homePage: 'http://missile.man', + name: 'steely.eyed', + }, + }); + // tslint:enable:object-literal-sort-keys + await createTextProfile({ agent: creationAgent }); + await getProfile({ agent: retrievalAgent }).expect(OK, TEST_CONTENT); + }); + + it('should get when getting json', async () => { + await createJsonProfile(); + await getProfile().expect(OK, JSON.parse(TEST_JSON_CONTENT)); + }); + + it('should get when not using mbox', async () => { + await createTextProfile({ agent: TEST_MBOX_AGENT }); + await getProfile({ agent: JSON.stringify(TEST_MBOX_AGENT) }) + .expect(OK, TEST_CONTENT); + }); + + it('should get when not using mbox_sha1sum', async () => { + await createTextProfile({ agent: TEST_MBOXSHA1_AGENT }); + await getProfile({ agent: JSON.stringify(TEST_MBOXSHA1_AGENT) }) + .expect(OK, TEST_CONTENT); + }); + + it('should get when not using openid', async () => { + await createTextProfile({ agent: TEST_OPENID_AGENT }); + await getProfile({ agent: JSON.stringify(TEST_OPENID_AGENT) }) + .expect(OK, TEST_CONTENT); + }); + + it('should get when not using account', async () => { + await createTextProfile({ agent: TEST_ACCOUNT_AGENT }); + await getProfile({ agent: JSON.stringify(TEST_ACCOUNT_AGENT) }) + .expect(OK, TEST_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfile/getNonExistingProfile.test.ts b/src/apps/agents/expressPresenter/tests/getProfile/getNonExistingProfile.test.ts new file mode 100644 index 000000000..797ba3877 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/getNonExistingProfile.test.ts @@ -0,0 +1,31 @@ +import { BAD_REQUEST, NOT_FOUND } from 'http-status-codes'; +import { + TEST_INVALID_AGENT, + TEST_INVALID_JSON_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getProfile from './utils/getProfile'; + +describe('expressPresenter.getProfile with non-existing model', () => { + setup(); + + it('should error when getting a non-existing model', async () => { + await getProfile().expect(NOT_FOUND); + }); + + it('should throw warnings when using an invalid agent', async () => { + await getProfile({ + agent: JSON.stringify(TEST_INVALID_AGENT), + }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the agent', async () => { + await getProfile({ agent: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when using invalid json in agent', async () => { + await getProfile({ + agent: TEST_INVALID_JSON_CONTENT, + }).expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfile/getOutsideOrg.test.ts b/src/apps/agents/expressPresenter/tests/getProfile/getOutsideOrg.test.ts new file mode 100644 index 000000000..a2c27ef20 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/getOutsideOrg.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_ORG } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('expressPresenter.getProfile outside the organisation', () => { + setup(); + + it('should error when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); + + it('should error when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfile/getOutsideStore.test.ts b/src/apps/agents/expressPresenter/tests/getProfile/getOutsideStore.test.ts new file mode 100644 index 000000000..0af32ad91 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/getOutsideStore.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_STORE } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('expressPresenter.getProfile outside the store', () => { + setup(); + + it('should error when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); + + it('should error when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfile/getWithScopes.test.ts b/src/apps/agents/expressPresenter/tests/getProfile/getWithScopes.test.ts new file mode 100644 index 000000000..e407a73f5 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/getWithScopes.test.ts @@ -0,0 +1,29 @@ +import { FORBIDDEN, NOT_FOUND } from 'http-status-codes'; +import { + TEST_EXPIRED_ORG_TOKEN, + TEST_INVALID_SCOPE_TOKEN, + TEST_UNTRUSTED_TOKEN, + TEST_VALID_SCOPE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getProfile from './utils/getProfile'; + +describe('expressPresenter.getProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + await getProfile().set('Authorization', TEST_INVALID_SCOPE_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using expired client', async () => { + await getProfile().set('Authorization', TEST_EXPIRED_ORG_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using untrusted client', async () => { + await getProfile().set('Authorization', TEST_UNTRUSTED_TOKEN).expect(FORBIDDEN); + }); + + it('should throw no model error when using valid scopes', async () => { + await getProfile().set('Authorization', TEST_VALID_SCOPE_TOKEN).expect(NOT_FOUND); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfile/utils/assertOutsideClient.ts b/src/apps/agents/expressPresenter/tests/getProfile/utils/assertOutsideClient.ts new file mode 100644 index 000000000..0fada58ae --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/utils/assertOutsideClient.ts @@ -0,0 +1,6 @@ +import { NOT_FOUND } from 'http-status-codes'; +import getProfile from './getProfile'; + +export default async () => { + await getProfile().expect(NOT_FOUND); +}; diff --git a/src/apps/agents/expressPresenter/tests/getProfile/utils/getProfile.ts b/src/apps/agents/expressPresenter/tests/getProfile/utils/getProfile.ts new file mode 100644 index 000000000..6ef59c2d5 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfile/utils/getProfile.ts @@ -0,0 +1,15 @@ +import { Test } from 'supertest'; +import { route, xapiHeaderVersion } from '../../../../utils/constants'; +import { TEST_MBOX_AGENT, TEST_PROFILE_ID } from '../../../../utils/testValues'; +import supertest from '../../utils/supertest'; + +export default (optsOverrides: object = {}): Test => { + return supertest + .get(`${route}/profile`) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/alternateRequest.test.ts b/src/apps/agents/expressPresenter/tests/getProfiles/alternateRequest.test.ts new file mode 100644 index 000000000..1f700aac0 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/alternateRequest.test.ts @@ -0,0 +1,21 @@ +import { OK } from 'http-status-codes'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import { + ALTERNATE_CONTENT_TYPE, + TEST_MBOX_AGENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('expressPresenter.getProfiles using the alternate request syntax', () => { + const { supertest } = setup(); + + it('should return no profile ids when getting a non-existing activity id', async () => { + await supertest + .post(`${route}/profile`) + .set('Content-Type', ALTERNATE_CONTENT_TYPE) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ method: 'GET' }) + .send({ agent: JSON.stringify(TEST_MBOX_AGENT) }) + .expect(OK, []); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/getExistingProfiles.test.ts b/src/apps/agents/expressPresenter/tests/getProfiles/getExistingProfiles.test.ts new file mode 100644 index 000000000..4d388f1ec --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/getExistingProfiles.test.ts @@ -0,0 +1,44 @@ +import { OK } from 'http-status-codes'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + TEST_ACCOUNT_AGENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getProfiles from './utils/getProfiles'; + +describe('expressPresenter.getProfiles with existing model', () => { + setup(); + + it('should return profile ids when getting a existing model', async () => { + await createTextProfile(); + await getProfiles().expect(OK, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an mbox', async () => { + await createTextProfile({ agent: TEST_MBOX_AGENT }); + await getProfiles({ agent: JSON.stringify(TEST_MBOX_AGENT) }) + .expect(OK, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an mbox_sha1sum', async () => { + await createTextProfile({ agent: TEST_MBOXSHA1_AGENT }); + await getProfiles({ agent: JSON.stringify(TEST_MBOXSHA1_AGENT) }) + .expect(OK, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an openid', async () => { + await createTextProfile({ agent: TEST_OPENID_AGENT }); + await getProfiles({ agent: JSON.stringify(TEST_OPENID_AGENT) }) + .expect(OK, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an account', async () => { + await createTextProfile({ agent: TEST_ACCOUNT_AGENT }); + await getProfiles({ agent: JSON.stringify(TEST_ACCOUNT_AGENT) }) + .expect(OK, [TEST_PROFILE_ID]); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/getNonExistingProfiles.test.ts b/src/apps/agents/expressPresenter/tests/getProfiles/getNonExistingProfiles.test.ts new file mode 100644 index 000000000..1945aac8a --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/getNonExistingProfiles.test.ts @@ -0,0 +1,34 @@ +import { BAD_REQUEST, OK } from 'http-status-codes'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + TEST_INVALID_AGENT, + TEST_INVALID_JSON_CONTENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getProfiles from './utils/getProfiles'; + +describe('expressPresenter.getProfiles with existing model', () => { + setup(); + + it('should return profile ids when getting a existing model', async () => { + await createTextProfile(); + await getProfiles().expect(OK, [TEST_PROFILE_ID]); + }); + + it('should throw warnings when using an invalid agent', async () => { + await getProfiles({ + agent: JSON.stringify(TEST_INVALID_AGENT), + }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the agent', async () => { + await getProfiles({ agent: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when using invalid json in agent', async () => { + await getProfiles({ + agent: TEST_INVALID_JSON_CONTENT, + }).expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/getOutsideOrg.test.ts b/src/apps/agents/expressPresenter/tests/getProfiles/getOutsideOrg.test.ts new file mode 100644 index 000000000..d0f8a3337 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/getOutsideOrg.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_ORG } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('expressPresenter.getProfiles outside the organisation', () => { + setup(); + + it('should return no profile ids when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); + + it('should return no profile ids when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/getOutsideStore.test.ts b/src/apps/agents/expressPresenter/tests/getProfiles/getOutsideStore.test.ts new file mode 100644 index 000000000..a3c8a6ea8 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/getOutsideStore.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_STORE } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('expressPresenter.getProfiles outside the store', () => { + setup(); + + it('should return no profile ids when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); + + it('should return no profile ids when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/getWithScopes.test.ts b/src/apps/agents/expressPresenter/tests/getProfiles/getWithScopes.test.ts new file mode 100644 index 000000000..ad576d17e --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/getWithScopes.test.ts @@ -0,0 +1,29 @@ +import { FORBIDDEN, OK } from 'http-status-codes'; +import { + TEST_EXPIRED_ORG_TOKEN, + TEST_INVALID_SCOPE_TOKEN, + TEST_UNTRUSTED_TOKEN, + TEST_VALID_SCOPE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getProfiles from './utils/getProfiles'; + +describe('expressPresenter.getProfiles with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + await getProfiles().set('Authorization', TEST_INVALID_SCOPE_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using expired client', async () => { + await getProfiles().set('Authorization', TEST_EXPIRED_ORG_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using untrusted client', async () => { + await getProfiles().set('Authorization', TEST_UNTRUSTED_TOKEN).expect(FORBIDDEN); + }); + + it('should return no models when using valid scopes', async () => { + await getProfiles().set('Authorization', TEST_VALID_SCOPE_TOKEN).expect(OK, []); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/getWithSince.test.ts b/src/apps/agents/expressPresenter/tests/getProfiles/getWithSince.test.ts new file mode 100644 index 000000000..4b4b60e27 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/getWithSince.test.ts @@ -0,0 +1,28 @@ +import { delay } from 'bluebird'; +import { OK } from 'http-status-codes'; +import createTextProfile from '../../../utils/createTextProfile'; +import { TEST_PROFILE_ID } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getProfiles from './utils/getProfiles'; + +const TEST_DELAY_MS = 2; + +describe('expressPresenter.getProfiles with since', () => { + setup(); + + it('should return no profile ids when updated before since', async () => { + await createTextProfile(); + await Promise.resolve(delay(TEST_DELAY_MS)); + const timestamp = new Date(); + await getProfiles({ since: timestamp.toISOString() }).expect(OK, []); + }); + + it('should return the profile id when updated after since', async () => { + const timestamp = new Date(); + await Promise.resolve(delay(TEST_DELAY_MS)); + await createTextProfile(); + await getProfiles({ + since: timestamp.toISOString(), + }).expect(OK, [TEST_PROFILE_ID]); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/utils/assertOutsideClient.ts b/src/apps/agents/expressPresenter/tests/getProfiles/utils/assertOutsideClient.ts new file mode 100644 index 000000000..b8e4274f8 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/utils/assertOutsideClient.ts @@ -0,0 +1,6 @@ +import { OK } from 'http-status-codes'; +import getProfiles from './getProfiles'; + +export default async () => { + await getProfiles().expect(OK, []); +}; diff --git a/src/apps/agents/expressPresenter/tests/getProfiles/utils/getProfiles.ts b/src/apps/agents/expressPresenter/tests/getProfiles/utils/getProfiles.ts new file mode 100644 index 000000000..cd2e6c641 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/getProfiles/utils/getProfiles.ts @@ -0,0 +1,14 @@ +import { Test } from 'supertest'; +import { route, xapiHeaderVersion } from '../../../../utils/constants'; +import { TEST_MBOX_AGENT } from '../../../../utils/testValues'; +import supertest from '../../utils/supertest'; + +export default (optsOverrides: object = {}): Test => { + return supertest + .get(`${route}/profile`) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + agent: JSON.stringify(TEST_MBOX_AGENT), + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/expressPresenter/tests/postProfile/alternateRequest.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/alternateRequest.test.ts new file mode 100644 index 000000000..075d6d2ad --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/alternateRequest.test.ts @@ -0,0 +1,39 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import createObjectProfile from '../../../utils/createObjectProfile'; +import getTestProfile from '../../../utils/getTestProfile'; +import { + ALTERNATE_CONTENT_TYPE, + JSON_CONTENT_TYPE, + TEST_MBOX_AGENT, + TEST_OBJECT_MERGED_CONTENT, + TEST_OBJECT_PATCH_CONTENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('expressPresenter.postProfile using the alternate request syntax', () => { + const { supertest } = setup(); + + it('should merge when patching with object content ', async () => { + await createObjectProfile(); + const getProfileResult = await getTestProfile(); + await supertest + .post(`${route}/profile`) + .set('Content-Type', ALTERNATE_CONTENT_TYPE) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + method: 'POST', + }) + .send({ + 'Content-Type': JSON_CONTENT_TYPE, + 'If-Match': getProfileResult.etag, + agent: JSON.stringify(TEST_MBOX_AGENT), + content: TEST_OBJECT_PATCH_CONTENT, + profileId: TEST_PROFILE_ID, + }) + .expect(NO_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/contentType.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/contentType.test.ts new file mode 100644 index 000000000..373623cbb --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/contentType.test.ts @@ -0,0 +1,50 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import { + ALTERNATE_CONTENT_TYPE, + JSON_CONTENT_TYPE, + TEST_MBOX_AGENT, + TEST_OBJECT_CONTENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +// These are regression tests for LearningLocker/learninglocker#999. +describe(__filename, () => { + const { supertest } = setup(); + + it('should not error when using a charset for JSON ', async () => { + await supertest + .post(`${route}/profile`) + .set('Content-Type', `${JSON_CONTENT_TYPE}; charset=UTF-8`) + .set('X-Experience-API-Version', xapiHeaderVersion) + .set('If-None-Match', '*') + .query({ + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, + }) + .send(TEST_OBJECT_CONTENT) + .expect(NO_CONTENT); + await assertProfile(TEST_OBJECT_CONTENT); + }); + + it('should not error when using a charset for alternate requests ', async () => { + await supertest + .post(`${route}/profile`) + .set('Content-Type', `${ALTERNATE_CONTENT_TYPE}; charset=UTF-8`) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + method: 'POST', + }) + .send({ + 'Content-Type': JSON_CONTENT_TYPE, + 'If-None-Match': '*', + agent: JSON.stringify(TEST_MBOX_AGENT), + content: TEST_OBJECT_CONTENT, + profileId: TEST_PROFILE_ID, + }) + .expect(NO_CONTENT); + await assertProfile(TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/patchExistingJson.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/patchExistingJson.test.ts new file mode 100644 index 000000000..859eb7262 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/patchExistingJson.test.ts @@ -0,0 +1,30 @@ +import { BAD_REQUEST } from 'http-status-codes'; +import createJsonProfile from '../../../utils/createJsonProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_OBJECT_CONTENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; + +describe('expressPresenter.postProfile with existing JSON content', () => { + setup(); + + it('should error when patching with text content', async () => { + await createJsonProfile(); + await patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should error when patching with JSON content', async () => { + await createJsonProfile(); + await patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should error when patching with object content', async () => { + await createJsonProfile(); + await patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE).expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/patchExistingObject.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/patchExistingObject.test.ts new file mode 100644 index 000000000..6e17db07f --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/patchExistingObject.test.ts @@ -0,0 +1,71 @@ +import { BAD_REQUEST } from 'http-status-codes'; +import assertImmutableProfile from '../../../utils/assertImmutableProfile'; +import assertProfile from '../../../utils/assertProfile'; +import createImmutableProfile from '../../../utils/createImmutableProfile'; +import createObjectProfile from '../../../utils/createObjectProfile'; +import { + JSON_CONTENT_TYPE, + TEST_ACCOUNT_AGENT, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OBJECT_MERGED_CONTENT, + TEST_OBJECT_PATCH_CONTENT, + TEST_OPENID_AGENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; +import patchExistingProfile from './utils/patchExistingProfile'; + +describe('expressPresenter.postProfile with existing object content', () => { + setup(); + + it('should error when patching with text content', async () => { + await createObjectProfile(); + await patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should error when patching with JSON content', async () => { + await createObjectProfile(); + await patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should merge when patching with object content', async () => { + await createObjectProfile(); + await patchExistingProfile(TEST_MBOX_AGENT, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT); + }); + + it('should not patch existing models when patching a non-existing model', async () => { + await createObjectProfile(); + await createImmutableProfile(); + await patchExistingProfile(TEST_MBOX_AGENT); + await assertImmutableProfile(); + }); + + it('should merge when patching with mbox', async () => { + await createObjectProfile({ agent: TEST_MBOX_AGENT }); + await patchExistingProfile(TEST_MBOX_AGENT, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_MBOX_AGENT }); + }); + + it('should merge when patching with mbox_sha1sum', async () => { + await createObjectProfile({ agent: TEST_MBOXSHA1_AGENT }); + await patchExistingProfile(TEST_MBOXSHA1_AGENT, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_MBOXSHA1_AGENT }); + }); + + it('should merge when patching with openid', async () => { + await createObjectProfile({ agent: TEST_OPENID_AGENT }); + await patchExistingProfile(TEST_OPENID_AGENT, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_OPENID_AGENT }); + }); + + it('should merge when patching with account', async () => { + await createObjectProfile({ agent: TEST_ACCOUNT_AGENT }); + await patchExistingProfile(TEST_ACCOUNT_AGENT, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_ACCOUNT_AGENT }); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/patchExistingText.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/patchExistingText.test.ts new file mode 100644 index 000000000..6f6a0f0be --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/patchExistingText.test.ts @@ -0,0 +1,30 @@ +import { BAD_REQUEST } from 'http-status-codes'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_OBJECT_CONTENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; + +describe('expressPresenter.postProfile with existing text content', () => { + setup(); + + it('should error when patching with text content', async () => { + await createTextProfile(); + await patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should error when patching with JSON content', async () => { + await createTextProfile(); + await patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should error when patching with object content', async () => { + await createTextProfile(); + await patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE).expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/patchNewContent.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/patchNewContent.test.ts new file mode 100644 index 000000000..75f71fb23 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/patchNewContent.test.ts @@ -0,0 +1,56 @@ +import { BAD_REQUEST, NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_INVALID_AGENT, + TEST_INVALID_JSON_CONTENT, + TEST_JSON_CONTENT, + TEST_OBJECT_CONTENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; +import patchProfile from './utils/patchProfile'; + +describe('expressPresenter.postProfile with new content', () => { + setup(); + + it('should error when patching with text content', async () => { + await patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should error when patching with JSON content', async () => { + await patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE).expect(BAD_REQUEST); + }); + + it('should create when patching with object content', async () => { + await patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE).expect(NO_CONTENT); + await assertProfile(TEST_OBJECT_CONTENT); + }); + + it('should throw warnings when using an invalid agent', async () => { + await patchProfile({ + agent: JSON.stringify(TEST_INVALID_AGENT), + }).expect(BAD_REQUEST); + }); + + it('should throw warnings when using invalid json in agent', async () => { + await patchProfile({ + agent: TEST_INVALID_JSON_CONTENT, + }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the agent', async () => { + await patchProfile({ agent: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the profile id', async () => { + await patchProfile({ profileId: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when using an invalid json content', async () => { + await patchContent(TEST_INVALID_JSON_CONTENT, JSON_CONTENT_TYPE) + .expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/patchOutsideClient.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/patchOutsideClient.test.ts new file mode 100644 index 000000000..d85fb6afe --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/patchOutsideClient.test.ts @@ -0,0 +1,34 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { + JSON_CONTENT_TYPE, + TEST_OBJECT_CONTENT, + TEST_OBJECT_PATCH_CONTENT, + TEST_OUTSIDE_ORG_TOKEN, + TEST_OUTSIDE_STORE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; +import patchProfile from './utils/patchProfile'; + +describe('expressPresenter.postProfile when outside client', () => { + setup(); + + const patchOutsideClient = async (token: string) => { + await patchProfile({}, TEST_OBJECT_PATCH_CONTENT) + .set('Authorization', token) + .expect(NO_CONTENT); + }; + + it('should not overwrite existing model when using a different organisation', async () => { + await patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE); + await patchOutsideClient(TEST_OUTSIDE_ORG_TOKEN); + await assertProfile(TEST_OBJECT_CONTENT); + }); + + it('should not overwrite existing model when using a different store', async () => { + await patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE); + await patchOutsideClient(TEST_OUTSIDE_STORE_TOKEN); + await assertProfile(TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/patchWithEtags.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/patchWithEtags.test.ts new file mode 100644 index 000000000..0dd8ca81f --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/patchWithEtags.test.ts @@ -0,0 +1,44 @@ +import { BAD_REQUEST, NO_CONTENT, PRECONDITION_FAILED } from 'http-status-codes'; +import createObjectProfile from '../../../utils/createObjectProfile'; +import getTestProfile from '../../../utils/getTestProfile'; +import setup from '../utils/setup'; +import patchProfile from './utils/patchProfile'; + +describe('expressPresenter.postProfile with etags', () => { + setup(); + + it('should allow patches when using a correct etag', async () => { + await createObjectProfile(); + const getProfileResult = await getTestProfile(); + await patchProfile() + .set('If-Match', getProfileResult.etag) + .expect(NO_CONTENT); + }); + + it('should throw precondition error when using an incorrect ifMatch', async () => { + await createObjectProfile(); + await patchProfile() + .set('If-Match', 'incorrect_etag') + .expect(PRECONDITION_FAILED); + }); + + it('should throw precondition error when using an incorrect ifNoneMatch', async () => { + await createObjectProfile(); + await patchProfile() + .set('If-None-Match', '*') + .expect(PRECONDITION_FAILED); + }); + + it('should allow patch when not using an ifMatch or ifNoneMatch', async () => { + await createObjectProfile(); + await patchProfile().expect(NO_CONTENT); + }); + + it('should throw max etag error when using ifMatch and ifNoneMatch', async () => { + await createObjectProfile(); + await patchProfile() + .set('If-Match', 'incorrect_etag') + .set('If-None-Match', '*') + .expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/patchWithScopes.test.ts b/src/apps/agents/expressPresenter/tests/postProfile/patchWithScopes.test.ts new file mode 100644 index 000000000..85626c648 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/patchWithScopes.test.ts @@ -0,0 +1,29 @@ +import { FORBIDDEN, NO_CONTENT } from 'http-status-codes'; +import { + TEST_EXPIRED_ORG_TOKEN, + TEST_INVALID_SCOPE_TOKEN, + TEST_UNTRUSTED_TOKEN, + TEST_VALID_SCOPE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchProfile from './utils/patchProfile'; + +describe('expressPresenter.postProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + await patchProfile().set('Authorization', TEST_INVALID_SCOPE_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using expired client', async () => { + await patchProfile().set('Authorization', TEST_EXPIRED_ORG_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using untrusted client', async () => { + await patchProfile().set('Authorization', TEST_UNTRUSTED_TOKEN).expect(FORBIDDEN); + }); + + it('should not throw an error when using valid scopes', async () => { + await patchProfile().set('Authorization', TEST_VALID_SCOPE_TOKEN).expect(NO_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/postProfile/utils/patchContent.ts b/src/apps/agents/expressPresenter/tests/postProfile/utils/patchContent.ts new file mode 100644 index 000000000..56132ffa1 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/utils/patchContent.ts @@ -0,0 +1,6 @@ +import { Test } from 'supertest'; +import patchProfile from './patchProfile'; + +export default (content: string, contentType: string): Test => { + return patchProfile({}, content, contentType); +}; diff --git a/src/apps/agents/expressPresenter/tests/postProfile/utils/patchExistingProfile.ts b/src/apps/agents/expressPresenter/tests/postProfile/utils/patchExistingProfile.ts new file mode 100644 index 000000000..3453a9505 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/utils/patchExistingProfile.ts @@ -0,0 +1,15 @@ +import { NO_CONTENT } from 'http-status-codes'; +import Agent from '../../../../models/Agent'; +import getTestProfile from '../../../../utils/getTestProfile'; +import { TEST_OBJECT_CONTENT } from '../../../../utils/testValues'; +import patchProfile from './patchProfile'; + +export default async ( + agent: Agent, + content: string = TEST_OBJECT_CONTENT, +) => { + const getProfileResult = await getTestProfile({ agent }); + await patchProfile({ agent: JSON.stringify(agent) }, content) + .set('If-Match', getProfileResult.etag) + .expect(NO_CONTENT); +}; diff --git a/src/apps/agents/expressPresenter/tests/postProfile/utils/patchProfile.ts b/src/apps/agents/expressPresenter/tests/postProfile/utils/patchProfile.ts new file mode 100644 index 000000000..a4dc0ddb4 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/postProfile/utils/patchProfile.ts @@ -0,0 +1,26 @@ +import { Test } from 'supertest'; +import { route, xapiHeaderVersion } from '../../../../utils/constants'; +import { + JSON_CONTENT_TYPE, + TEST_MBOX_AGENT, + TEST_OBJECT_CONTENT, + TEST_PROFILE_ID, +} from '../../../../utils/testValues'; +import supertest from '../../utils/supertest'; + +export default ( + optsOverrides: object = {}, + content: string = TEST_OBJECT_CONTENT, + contentType: string = JSON_CONTENT_TYPE, +): Test => { + return supertest + .post(`${route}/profile`) + .set('Content-Type', contentType) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }) + .send(content); +}; diff --git a/src/apps/agents/expressPresenter/tests/putProfile/alternateRequest.test.ts b/src/apps/agents/expressPresenter/tests/putProfile/alternateRequest.test.ts new file mode 100644 index 000000000..18e682306 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/alternateRequest.test.ts @@ -0,0 +1,34 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import { + ALTERNATE_CONTENT_TYPE, + TEST_CONTENT, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('expressPresenter.putProfile using the alternate request syntax', () => { + const { supertest } = setup(); + + it('should create when using valid activity id', async () => { + await supertest + .post(`${route}/profile`) + .set('Content-Type', ALTERNATE_CONTENT_TYPE) + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + method: 'PUT', + }) + .send({ + 'Content-Type': TEXT_CONTENT_TYPE, + 'If-None-Match': '*', + agent: JSON.stringify(TEST_MBOX_AGENT), + content: TEST_CONTENT, + profileId: TEST_PROFILE_ID, + }) + .expect(NO_CONTENT); + await assertProfile(TEST_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/putProfile/contentType.test.ts b/src/apps/agents/expressPresenter/tests/putProfile/contentType.test.ts new file mode 100644 index 000000000..15e8a853c --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/contentType.test.ts @@ -0,0 +1,30 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { route, xapiHeaderVersion } from '../../../utils/constants'; +import { + JSON_CONTENT_TYPE, + TEST_MBOX_AGENT, + TEST_OBJECT_CONTENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +// These are regression tests for LearningLocker/learninglocker#999. +describe(__filename, () => { + const { supertest } = setup(); + + it('should not error when using a charset for JSON ', async () => { + await supertest + .put(`${route}/profile`) + .set('Content-Type', `${JSON_CONTENT_TYPE}; charset=UTF-8`) + .set('X-Experience-API-Version', xapiHeaderVersion) + .set('If-None-Match', '*') + .query({ + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, + }) + .send(TEST_OBJECT_CONTENT) + .expect(NO_CONTENT); + await assertProfile(TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/putProfile/overwriteExistingProfile.test.ts b/src/apps/agents/expressPresenter/tests/putProfile/overwriteExistingProfile.test.ts new file mode 100644 index 000000000..4f11b9f6f --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/overwriteExistingProfile.test.ts @@ -0,0 +1,54 @@ +import assertImmutableProfile from '../../../utils/assertImmutableProfile'; +import assertProfile from '../../../utils/assertProfile'; +import createImmutableProfile from '../../../utils/createImmutableProfile'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + TEST_ACCOUNT_AGENT, + TEST_IMMUTABLE_CONTENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, + } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteExistingProfile from './utils/overwriteExistingProfile'; + +describe('expressPresenter.putProfile with existing model', () => { + setup(); + + it('should overwrite model when overwriting an existing model', async () => { + await createTextProfile(); + await overwriteExistingProfile(TEST_MBOX_AGENT, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT); + }); + + it('should not overwrite non-matched models', async () => { + await createTextProfile(); + await createImmutableProfile(); + await overwriteExistingProfile(TEST_MBOX_AGENT); + await assertImmutableProfile(); + }); + + it('should overwrite model when overwriting with mbox', async () => { + await createTextProfile({ agent: TEST_MBOX_AGENT }); + await overwriteExistingProfile(TEST_MBOX_AGENT, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_MBOX_AGENT }); + }); + + it('should overwrite model when overwriting with mbox_sha1sum', async () => { + await createTextProfile({ agent: TEST_MBOXSHA1_AGENT }); + await overwriteExistingProfile(TEST_MBOXSHA1_AGENT, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_MBOXSHA1_AGENT }); + }); + + it('should overwrite model when overwriting with openid', async () => { + await createTextProfile({ agent: TEST_OPENID_AGENT }); + await overwriteExistingProfile(TEST_OPENID_AGENT, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_OPENID_AGENT }); + }); + + it('should overwrite model when overwriting with account', async () => { + await createTextProfile({ agent: TEST_ACCOUNT_AGENT }); + await overwriteExistingProfile(TEST_ACCOUNT_AGENT, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_ACCOUNT_AGENT }); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/putProfile/overwriteNonExistingProfile.test.ts b/src/apps/agents/expressPresenter/tests/putProfile/overwriteNonExistingProfile.test.ts new file mode 100644 index 000000000..c576803ba --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/overwriteNonExistingProfile.test.ts @@ -0,0 +1,44 @@ +import { BAD_REQUEST, NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_INVALID_AGENT, + TEST_INVALID_JSON_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('expressPresenter.putProfile with non-existing model', () => { + setup(); + + it('should create when using valid agent', async () => { + await overwriteProfile().expect(NO_CONTENT); + await assertProfile(TEST_CONTENT); + }); + + it('should throw warnings when using an invalid agent', async () => { + await overwriteProfile({ + agent: JSON.stringify(TEST_INVALID_AGENT), + }).expect(BAD_REQUEST); + }); + + it('should throw warnings when using invalid json in agent', async () => { + await overwriteProfile({ + agent: TEST_INVALID_JSON_CONTENT, + }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the agent', async () => { + await overwriteProfile({ agent: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when missing the profile id', async () => { + await overwriteProfile({ profileId: undefined }).expect(BAD_REQUEST); + }); + + it('should throw warnings when using an invalid json content', async () => { + await overwriteProfile({}, TEST_INVALID_JSON_CONTENT, JSON_CONTENT_TYPE) + .expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/putProfile/overwriteOutsideClient.test.ts b/src/apps/agents/expressPresenter/tests/putProfile/overwriteOutsideClient.test.ts new file mode 100644 index 000000000..4f583d414 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/overwriteOutsideClient.test.ts @@ -0,0 +1,31 @@ +import { NO_CONTENT } from 'http-status-codes'; +import assertProfile from '../../../utils/assertProfile'; +import { + TEST_CONTENT, + TEST_OUTSIDE_ORG_TOKEN, + TEST_OUTSIDE_STORE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('expressPresenter.putProfile when outside client', () => { + setup(); + + const overwriteOutsideProfile = async (token: string) => { + await overwriteProfile({}, 'unused_content') + .set('Authorization', token) + .expect(NO_CONTENT); + }; + + it('should not overwrite existing model when using a different organisation', async () => { + await overwriteProfile().expect(NO_CONTENT); + await overwriteOutsideProfile(TEST_OUTSIDE_ORG_TOKEN); + await assertProfile(TEST_CONTENT); + }); + + it('should not overwrite existing model when using a different store', async () => { + await overwriteProfile().expect(NO_CONTENT); + await overwriteOutsideProfile(TEST_OUTSIDE_STORE_TOKEN); + await assertProfile(TEST_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/putProfile/overwriteWithEtags.test.ts b/src/apps/agents/expressPresenter/tests/putProfile/overwriteWithEtags.test.ts new file mode 100644 index 000000000..ff8e3da9c --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/overwriteWithEtags.test.ts @@ -0,0 +1,54 @@ +import { BAD_REQUEST, CONFLICT, NO_CONTENT, PRECONDITION_FAILED } from 'http-status-codes'; +import createTextProfile from '../../../utils/createTextProfile'; +import getTestProfile from '../../../utils/getTestProfile'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('expressPresenter.putProfile with etags', () => { + setup(); + + it('should allow overwrites when using a correct etag', async () => { + await createTextProfile(); + const getProfileResult = await getTestProfile(); + await overwriteProfile() + .set('If-Match', getProfileResult.etag) + .unset('If-None-Match') + .expect(NO_CONTENT); + }); + + it('should throw precondition error when using an incorrect ifMatch', async () => { + await createTextProfile(); + await overwriteProfile() + .set('If-Match', 'incorrect_etag') + .unset('If-None-Match') + .expect(PRECONDITION_FAILED); + }); + + it('should throw precondition error when using an incorrect ifNoneMatch', async () => { + await createTextProfile(); + await overwriteProfile() + .set('If-None-Match', '*') + .expect(PRECONDITION_FAILED); + }); + + it('should throw conflict error when not using ifMatch or ifNoneMatch', async () => { + await createTextProfile(); + await overwriteProfile() + .unset('If-None-Match') + .expect(CONFLICT); + }); + + it('should throw max etag error when using ifMatch and ifNoneMatch', async () => { + await createTextProfile(); + await overwriteProfile() + .set('If-Match', 'incorrect_etag') + .set('If-None-Match', '*') + .expect(BAD_REQUEST); + }); + + it('should throw missing etags error when not using ifMatch and ifNoneMatch', async () => { + await overwriteProfile() + .unset('If-None-Match') + .expect(BAD_REQUEST); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/putProfile/overwriteWithScopes.test.ts b/src/apps/agents/expressPresenter/tests/putProfile/overwriteWithScopes.test.ts new file mode 100644 index 000000000..bc31821d9 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/overwriteWithScopes.test.ts @@ -0,0 +1,29 @@ +import { FORBIDDEN, NO_CONTENT } from 'http-status-codes'; +import { + TEST_EXPIRED_ORG_TOKEN, + TEST_INVALID_SCOPE_TOKEN, + TEST_UNTRUSTED_TOKEN, + TEST_VALID_SCOPE_TOKEN, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('expressPresenter.putProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + await overwriteProfile().set('Authorization', TEST_INVALID_SCOPE_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using expired client', async () => { + await overwriteProfile().set('Authorization', TEST_EXPIRED_ORG_TOKEN).expect(FORBIDDEN); + }); + + it('should throw forbidden error when using untrusted client', async () => { + await overwriteProfile().set('Authorization', TEST_UNTRUSTED_TOKEN).expect(FORBIDDEN); + }); + + it('should not throw an error when using valid scopes', async () => { + await overwriteProfile().set('Authorization', TEST_VALID_SCOPE_TOKEN).expect(NO_CONTENT); + }); +}); diff --git a/src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteExistingProfile.ts b/src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteExistingProfile.ts new file mode 100644 index 000000000..54cad0ce9 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteExistingProfile.ts @@ -0,0 +1,16 @@ +import { NO_CONTENT } from 'http-status-codes'; +import Agent from '../../../../models/Agent'; +import getTestProfile from '../../../../utils/getTestProfile'; +import { TEST_CONTENT } from '../../../../utils/testValues'; +import overwriteProfile from './overwriteProfile'; + +export default async ( + agent: Agent, + content: string = TEST_CONTENT, +) => { + const getProfileResult = await getTestProfile({ agent }); + await overwriteProfile({ agent: JSON.stringify(agent) }, content) + .set('If-Match', getProfileResult.etag) + .unset('If-None-Match') + .expect(NO_CONTENT); +}; diff --git a/src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteProfile.ts b/src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteProfile.ts new file mode 100644 index 000000000..dc7517996 --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/putProfile/utils/overwriteProfile.ts @@ -0,0 +1,27 @@ +import { Test } from 'supertest'; +import { route, xapiHeaderVersion } from '../../../../utils/constants'; +import { + TEST_CONTENT, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, + TEXT_CONTENT_TYPE, +} from '../../../../utils/testValues'; +import supertest from '../../utils/supertest'; + +export default ( + optsOverrides: object = {}, + content: string = TEST_CONTENT, + contentType: string = TEXT_CONTENT_TYPE, +): Test => { + return supertest + .put(`${route}/profile`) + .set('Content-Type', contentType) + .set('If-None-Match', '*') + .set('X-Experience-API-Version', xapiHeaderVersion) + .query({ + agent: JSON.stringify(TEST_MBOX_AGENT), + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }) + .send(content); +}; diff --git a/src/apps/agents/expressPresenter/tests/utils/setup.ts b/src/apps/agents/expressPresenter/tests/utils/setup.ts new file mode 100644 index 000000000..58e54614b --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/utils/setup.ts @@ -0,0 +1,17 @@ +import setupService from 'jscommons/dist/tests/utils/setupService'; +import { SuperTest, Test } from 'supertest'; +import Service from '../../../serviceFactory/Service'; +import service from '../../../utils/testService'; +import supertest from './supertest'; + +const setup = setupService(service); + +export interface Result { + readonly service: Service; + readonly supertest: SuperTest; +} + +export default (): Result => { + setup(); + return { service, supertest }; +}; diff --git a/src/apps/agents/expressPresenter/tests/utils/supertest.ts b/src/apps/agents/expressPresenter/tests/utils/supertest.ts new file mode 100644 index 000000000..ae290236d --- /dev/null +++ b/src/apps/agents/expressPresenter/tests/utils/supertest.ts @@ -0,0 +1,26 @@ +import express from 'express'; +import supertest from 'supertest'; +import config from '../../../../../config'; +import logger from '../../../../../logger'; +import tracker from '../../../../../tracker'; +import translatorFactory from '../../../translatorFactory'; +import { route } from '../../../utils/constants'; +import service from '../../../utils/testService'; +import presenterFacade from '../../index'; + +const app = express(); +const translator = translatorFactory(); +const presenter = presenterFacade({ + bodyParserLimit: config.express.bodyParserLimit, + customRoute: 'xAPI/agents/profile/status', + customRouteText: 'ok', + logger, + morganDirectory: config.express.morganDirectory, + service, + tracker, + translator, +}); + +app.use(route, presenter); + +export default supertest(app); diff --git a/src/apps/agents/expressPresenter/utils/alternateProfileRequest.ts b/src/apps/agents/expressPresenter/utils/alternateProfileRequest.ts new file mode 100644 index 000000000..bf9f57ebe --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/alternateProfileRequest.ts @@ -0,0 +1,62 @@ +import { Request, Response } from 'express'; +import { get, mapKeys } from 'lodash'; +import { parse as parseQueryString } from 'query-string'; +import streamToString from 'stream-to-string'; +import stringToStream from 'string-to-stream'; +import InvalidMethod from '../../errors/InvalidMethod'; +import Config from '../Config'; +import deleteProfileWithService from './deleteProfileWithService'; +import getWithService from './getWithService'; +import overwriteProfileWithService from './overwriteProfileWithService'; +import patchProfileWithService from './patchProfileWithService'; + +export interface Options { + readonly config: Config; + readonly method: string; + readonly req: Request; + readonly res: Response; +} + +const getQuery = async (stream: NodeJS.ReadableStream) => { + const body = await streamToString(stream); + const decodedBody = parseQueryString(body); + return decodedBody; +}; + +const getHeaders = (bodyParams: any, req: Request) => { + const reqHeaders = req.headers; + const lowerCaseBodyParams = mapKeys(bodyParams, (_value, key: string) => { + return key.toLowerCase(); + }); + return { ...reqHeaders, ...lowerCaseBodyParams }; +}; + +export default async ({ config, method, req, res }: Options) => { + switch (method) { + case 'POST': { + const query = await getQuery(req); + const headers = getHeaders(query, req); + const content = stringToStream(get(query, 'content', '') as string); + return patchProfileWithService({ query, headers, content, config, res }); + } + case 'GET': { + const query = await getQuery(req); + const headers = getHeaders(query, req); + return getWithService({ config, headers, query, res }); + } + case 'PUT': { + const query = await getQuery(req); + const headers = getHeaders(query, req); + const content = stringToStream(get(query, 'content', '') as string); + return overwriteProfileWithService({ query, headers, content, config, res }); + } + case 'DELETE': { + const query = await getQuery(req); + const headers = getHeaders(query, req); + return deleteProfileWithService({ config, headers, query, res }); + } + default: { + throw new InvalidMethod(method); + } + } +}; diff --git a/src/apps/agents/expressPresenter/utils/catchErrors.ts b/src/apps/agents/expressPresenter/utils/catchErrors.ts new file mode 100644 index 000000000..5c209d69e --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/catchErrors.ts @@ -0,0 +1,22 @@ +import { Request, Response } from 'express'; +import CommonHandler from 'jscommons/dist/expressPresenter/utils/Handler'; +import { v4 as uuid } from 'uuid'; +import Config from '../Config'; +import handleError from '../utils/handleError'; + +export default (config: Config, handler: CommonHandler) => { + return (req: Request, res: Response): void => { + handler(req, res).catch(async (err: any) => { + const tracker = await config.tracker; + const errorId = uuid(); + tracker('errorId', errorId); + config.logger.silly(`${errorId}: xapi-agents request`, { + headers: req.headers, + method: req.method, + query: req.query, + url: req.originalUrl, + }); + return handleError({ config, errorId, res, err }); + }); + }; +}; diff --git a/src/apps/agents/expressPresenter/utils/contentTypePatterns.ts b/src/apps/agents/expressPresenter/utils/contentTypePatterns.ts new file mode 100644 index 000000000..e53a3751b --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/contentTypePatterns.ts @@ -0,0 +1,7 @@ +export const jsonContentTypePattern = ( + /^application\/json/i +); + +export const alternateContentTypePattern = ( + /^application\/x-www-form-urlencoded/i +); diff --git a/src/apps/agents/expressPresenter/utils/deleteProfileWithService.ts b/src/apps/agents/expressPresenter/utils/deleteProfileWithService.ts new file mode 100644 index 000000000..f051e59dc --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/deleteProfileWithService.ts @@ -0,0 +1,31 @@ +import { Response } from 'express'; +import { NO_CONTENT } from 'http-status-codes'; +import { get } from 'lodash'; +import { xapiHeaderVersion } from '../../utils/constants'; +import Config from '../Config'; +import getAgent from './getAgent'; +import getClient from './getClient'; +import getEtag from './getEtag'; +import getProfileId from './getProfileId'; +import validateVersionHeader from './validateVersionHeader'; + +export interface Options { + readonly query: any; + readonly config: Config; + readonly headers: any; + readonly res: Response; +} + +export default async ({ query, config, headers, res }: Options) => { + const client = await getClient(config, get(headers, 'authorization', '')); + validateVersionHeader(get(headers, 'x-experience-api-version')); + + const ifMatch = getEtag(get(headers, 'if-match')); + const agent = getAgent(get(query, 'agent')); + const profileId = getProfileId(get(query, 'profileId')); + + await config.service.deleteProfile({ agent, client, profileId, ifMatch }); + res.status(NO_CONTENT); + res.setHeader('X-Experience-API-Version', xapiHeaderVersion); + res.send(); +}; diff --git a/src/apps/agents/expressPresenter/utils/getAgent.ts b/src/apps/agents/expressPresenter/utils/getAgent.ts new file mode 100644 index 000000000..3f70d3b60 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getAgent.ts @@ -0,0 +1,14 @@ +import { createRequiredWarning, Warnings } from 'rulr'; +import Agent from '../../models/Agent'; +import parseJson from '../../utils/parseJson'; + +const PATH = ['query', 'agent']; + +export default (agentParam: string|undefined): Agent => { + if (agentParam === undefined) { + const warnings = [createRequiredWarning(agentParam, PATH)]; + throw new Warnings({}, ['query'], warnings); + } + + return parseJson(agentParam, PATH) as Agent; +}; diff --git a/src/apps/agents/expressPresenter/utils/getClient.ts b/src/apps/agents/expressPresenter/utils/getClient.ts new file mode 100644 index 000000000..ab1cb461e --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getClient.ts @@ -0,0 +1,11 @@ +import ClientModel from '../../models/ClientModel'; +import Config from '../Config'; + +export default async (config: Config, authToken = ''): Promise => { + const { client } = await config.service.getClient({ authToken }); + const tracker = await config.tracker; + tracker('org_id', client.organisation); + tracker('lrs_id', client.lrs_id); + tracker('client_id', client._id); + return client; +}; diff --git a/src/apps/agents/expressPresenter/utils/getContentType.ts b/src/apps/agents/expressPresenter/utils/getContentType.ts new file mode 100644 index 000000000..dabc5abff --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getContentType.ts @@ -0,0 +1,17 @@ +import { createRequiredWarning, Warnings } from 'rulr'; +import { jsonContentType } from '../../utils/constants'; +import { jsonContentTypePattern } from './contentTypePatterns'; + +export default (contentTypeHeader: string|undefined) => { + /* istanbul ignore next - superagent always sends a content type */ + if (contentTypeHeader === undefined) { + const warnings = [createRequiredWarning(contentTypeHeader, ['headers', 'Content-Type'])]; + throw new Warnings({}, ['headers'], warnings); + } + + if (jsonContentTypePattern.test(contentTypeHeader)) { + return jsonContentType; + } + + return contentTypeHeader; +}; diff --git a/src/apps/agents/expressPresenter/utils/getEtag.ts b/src/apps/agents/expressPresenter/utils/getEtag.ts new file mode 100644 index 000000000..302326e05 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getEtag.ts @@ -0,0 +1,6 @@ +export default (etagHeader: string|undefined) => { + if (etagHeader === undefined) { + return undefined; + } + return etagHeader.replace(/\"/g, ''); +}; diff --git a/src/apps/agents/expressPresenter/utils/getHeader.ts b/src/apps/agents/expressPresenter/utils/getHeader.ts new file mode 100644 index 000000000..741e73a44 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getHeader.ts @@ -0,0 +1,8 @@ +import { Request } from 'express'; +import { defaultTo } from 'lodash'; + +const getHeader = (req: Request, name: string, defaultValue: any = undefined): string => { + return defaultTo(req.body[name], defaultTo(req.header(name), defaultValue)); +}; + +export default getHeader; diff --git a/src/apps/agents/expressPresenter/utils/getProfileFromService.ts b/src/apps/agents/expressPresenter/utils/getProfileFromService.ts new file mode 100644 index 000000000..265e03d92 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getProfileFromService.ts @@ -0,0 +1,33 @@ +import { Response } from 'express'; +import { OK } from 'http-status-codes'; +import { get } from 'lodash'; +import { xapiHeaderVersion } from '../../utils/constants'; +import Config from '../Config'; +import getAgent from './getAgent'; +import getClient from './getClient'; +import getProfileId from './getProfileId'; +import validateVersionHeader from './validateVersionHeader'; + +export interface Options { + readonly query: any; + readonly config: Config; + readonly headers: any; + readonly res: Response; +} + +export default async ({ query, config, headers, res }: Options) => { + const client = await getClient(config, get(headers, 'authorization', '')); + validateVersionHeader(get(headers, 'x-experience-api-version')); + + const agent = getAgent(get(query, 'agent')); + const profileId = getProfileId(get(query, 'profileId')); + + const getProfileResult = await config.service.getProfile({ agent, client, profileId }); + + res.status(OK); + res.setHeader('ETag', `"${getProfileResult.etag}"`); + res.setHeader('Last-Modified', getProfileResult.updatedAt.toISOString()); + res.setHeader('X-Experience-API-Version', xapiHeaderVersion); + res.setHeader('Content-Type', getProfileResult.contentType); + getProfileResult.content.pipe(res); +}; diff --git a/src/apps/agents/expressPresenter/utils/getProfileId.ts b/src/apps/agents/expressPresenter/utils/getProfileId.ts new file mode 100644 index 000000000..7b110e988 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getProfileId.ts @@ -0,0 +1,10 @@ +import { createRequiredWarning, Warnings } from 'rulr'; + +export default (profileIdParam: string|undefined) => { + if (profileIdParam === undefined) { + const warnings = [createRequiredWarning(profileIdParam, ['query', 'profileId'])]; + throw new Warnings({}, ['query'], warnings); + } + + return profileIdParam; +}; diff --git a/src/apps/agents/expressPresenter/utils/getProfilesFromService.ts b/src/apps/agents/expressPresenter/utils/getProfilesFromService.ts new file mode 100644 index 000000000..3eacb42fd --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getProfilesFromService.ts @@ -0,0 +1,29 @@ +import { Response } from 'express'; +import { OK } from 'http-status-codes'; +import { get } from 'lodash'; +import { xapiHeaderVersion } from '../../utils/constants'; +import Config from '../Config'; +import getAgent from './getAgent'; +import getClient from './getClient'; +import validateVersionHeader from './validateVersionHeader'; + +export interface Options { + readonly query: any; + readonly config: Config; + readonly headers: any; + readonly res: Response; +} + +export default async ({ query, config, headers, res }: Options) => { + const client = await getClient(config, get(headers, 'authorization', '')); + validateVersionHeader(get(headers, 'x-experience-api-version')); + + const agent = getAgent(get(query, 'agent')); + const since = get(query, 'since') as string | undefined; + + const getProfilesResult = await config.service.getProfiles({ agent, client, since }); + + res.status(OK); + res.setHeader('X-Experience-API-Version', xapiHeaderVersion); + res.json(getProfilesResult.profileIds); +}; diff --git a/src/apps/agents/expressPresenter/utils/getWithService.ts b/src/apps/agents/expressPresenter/utils/getWithService.ts new file mode 100644 index 000000000..568386bfd --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/getWithService.ts @@ -0,0 +1,22 @@ +import { Response } from 'express'; +import { get } from 'lodash'; +import Config from '../Config'; +import getProfileFromService from './getProfileFromService'; +import getProfilesFromService from './getProfilesFromService'; + +export interface Options { + readonly query: any; + readonly headers: any; + readonly config: Config; + readonly res: Response; +} + +export default async ({ config, query, res, headers }: Options) => { + const queryProfileId = get(query, 'profileId') as string | undefined; + + if (queryProfileId === undefined) { + return getProfilesFromService({ config, query, res, headers }); + } else { + return getProfileFromService({ config, query, res, headers }); + } +}; diff --git a/src/apps/agents/expressPresenter/utils/handleError.ts b/src/apps/agents/expressPresenter/utils/handleError.ts new file mode 100644 index 000000000..8cbd73feb --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/handleError.ts @@ -0,0 +1,106 @@ +// tslint:disable:max-file-line-count +import { Response } from 'express'; +import { BAD_REQUEST, CONFLICT, FORBIDDEN, PRECONDITION_FAILED } from 'http-status-codes'; +import commonErrorHandler from 'jscommons/dist/expressPresenter/utils/handleError'; +import { Options as CommonOptions } from 'jscommons/dist/expressPresenter/utils/handleError'; +import sendMessage from 'jscommons/dist/expressPresenter/utils/sendMessage'; +import sendObject from 'jscommons/dist/expressPresenter/utils/sendObject'; +import { Warnings } from 'rulr'; +import Conflict from '../../errors/Conflict'; +import ExpiredClientError from '../../errors/ExpiredClientError'; +import IfMatch from '../../errors/IfMatch'; +import IfNoneMatch from '../../errors/IfNoneMatch'; +import InvalidMethod from '../../errors/InvalidMethod'; +import JsonSyntaxError from '../../errors/JsonSyntaxError'; +import MaxEtags from '../../errors/MaxEtags'; +import MissingEtags from '../../errors/MissingEtags'; +import NonJsonObject from '../../errors/NonJsonObject'; +import UntrustedClientError from '../../errors/UntrustedClientError'; +import { xapiHeaderVersion } from '../../utils/constants'; +import Config from '../Config'; +import translateWarning from './translateWarning'; + +export interface Options extends CommonOptions { + readonly config: Config; +} + +export default ({ config, errorId, res, err }: Options): Response => { + const { logger, translator } = config; + const logError = (msg: string, meta?: any) => { + logger.error(`${errorId}: xapi-agents handled - ${msg}`, meta); + }; + + res.setHeader('X-Experience-API-Version', xapiHeaderVersion); + + if (err instanceof MissingEtags) { + const code = BAD_REQUEST; + const message = translator.missingEtagsError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof JsonSyntaxError) { + const code = BAD_REQUEST; + const message = translator.jsonSyntaxError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof MaxEtags) { + const code = BAD_REQUEST; + const message = translator.maxEtagsError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof Conflict) { + const code = CONFLICT; + const message = translator.conflictError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof IfMatch) { + const code = PRECONDITION_FAILED; + const message = translator.ifMatchError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof IfNoneMatch) { + const code = PRECONDITION_FAILED; + const message = translator.ifNoneMatchError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof NonJsonObject) { + const code = BAD_REQUEST; + const message = translator.nonJsonObjectError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof Warnings) { + const code = 400; + const warnings = err.warnings; + const strWarnings = warnings.map((warning) => { + return translateWarning(translator, warning); + }); + const obj = { warnings: strWarnings }; + logError('Validation warnings', strWarnings); + return sendObject({ res, code, errorId, obj }); + } + if (err instanceof InvalidMethod) { + const code = BAD_REQUEST; + const message = translator.invalidMethodError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof ExpiredClientError) { + const code = FORBIDDEN; + const message = translator.expiredClientError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + if (err instanceof UntrustedClientError) { + const code = FORBIDDEN; + const message = translator.untrustedClientError(err); + logError(message); + return sendMessage({ res, code, errorId, message }); + } + return commonErrorHandler({ config, errorId, res, err }); +}; diff --git a/src/apps/agents/expressPresenter/utils/overwriteProfileWithService.ts b/src/apps/agents/expressPresenter/utils/overwriteProfileWithService.ts new file mode 100644 index 000000000..d97134654 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/overwriteProfileWithService.ts @@ -0,0 +1,44 @@ +import { Response } from 'express'; +import { NO_CONTENT } from 'http-status-codes'; +import { get } from 'lodash'; +import { xapiHeaderVersion } from '../../utils/constants'; +import Config from '../Config'; +import getAgent from './getAgent'; +import getClient from './getClient'; +import getContentType from './getContentType'; +import getEtag from './getEtag'; +import getProfileId from './getProfileId'; +import validateVersionHeader from './validateVersionHeader'; + +export interface Options { + readonly query: any; + readonly config: Config; + readonly headers: any; + readonly res: Response; + readonly content: NodeJS.ReadableStream; +} + +export default async ({ query, config, headers, res, content }: Options) => { + const client = await getClient(config, get(headers, 'authorization', '')); + validateVersionHeader(get(headers, 'x-experience-api-version')); + + const contentType = getContentType(get(headers, 'content-type')); + const ifMatch = getEtag(get(headers, 'if-match')); + const ifNoneMatch = getEtag(get(headers, 'if-none-match')); + const agent = getAgent(get(query, 'agent')); + const profileId = getProfileId(get(query, 'profileId')); + + await config.service.overwriteProfile({ + agent, + client, + content, + contentType, + ifMatch, + ifNoneMatch, + profileId, + }); + + res.status(NO_CONTENT); + res.setHeader('X-Experience-API-Version', xapiHeaderVersion); + res.send(); +}; diff --git a/src/apps/agents/expressPresenter/utils/patchProfileWithService.ts b/src/apps/agents/expressPresenter/utils/patchProfileWithService.ts new file mode 100644 index 000000000..c2a317a83 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/patchProfileWithService.ts @@ -0,0 +1,44 @@ +import { Response } from 'express'; +import { NO_CONTENT } from 'http-status-codes'; +import { get } from 'lodash'; +import { xapiHeaderVersion } from '../../utils/constants'; +import Config from '../Config'; +import getAgent from './getAgent'; +import getClient from './getClient'; +import getContentType from './getContentType'; +import getEtag from './getEtag'; +import getProfileId from './getProfileId'; +import validateVersionHeader from './validateVersionHeader'; + +export interface Options { + readonly query: any; + readonly config: Config; + readonly headers: any; + readonly res: Response; + readonly content: NodeJS.ReadableStream; +} + +export default async ({ query, config, headers, res, content }: Options) => { + const client = await getClient(config, get(headers, 'authorization', '')); + validateVersionHeader(get(headers, 'x-experience-api-version')); + + const contentType = getContentType(get(headers, 'content-type')); + const ifMatch = getEtag(get(headers, 'if-match')); + const ifNoneMatch = getEtag(get(headers, 'if-none-match')); + const agent = getAgent(get(query, 'agent')); + const profileId = getProfileId(get(query, 'profileId')); + + await config.service.patchProfile({ + agent, + client, + content, + contentType, + ifMatch, + ifNoneMatch, + profileId, + }); + + res.status(NO_CONTENT); + res.setHeader('X-Experience-API-Version', xapiHeaderVersion); + res.send(); +}; diff --git a/src/apps/agents/expressPresenter/utils/translateWarning.ts b/src/apps/agents/expressPresenter/utils/translateWarning.ts new file mode 100644 index 000000000..674e50120 --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/translateWarning.ts @@ -0,0 +1,13 @@ +import TypeWarning from '@learninglocker/xapi-validation/dist/warnings/TypeWarning'; +import commonTranslateWarning from 'jscommons/dist/expressPresenter/utils/translateWarning'; +import { Warning } from 'rulr'; +import Translator from '../../translatorFactory/Translator'; + +export default (translator: Translator, warning: Warning) => { + switch (warning.constructor) { + case TypeWarning: + return translator.xapiTypeWarning(warning as TypeWarning); + default: + return commonTranslateWarning(translator, warning); + } +}; diff --git a/src/apps/agents/expressPresenter/utils/validateVersionHeader.ts b/src/apps/agents/expressPresenter/utils/validateVersionHeader.ts new file mode 100644 index 000000000..baaee8fed --- /dev/null +++ b/src/apps/agents/expressPresenter/utils/validateVersionHeader.ts @@ -0,0 +1,7 @@ +import { version as validateVersion } from '@learninglocker/xapi-validation/dist/factory'; +import * as rulr from 'rulr'; + +const versionHeaderValidator = rulr.maybe(rulr.required(validateVersion)); +export default (headerVal?: string) => { + versionHeaderValidator(headerVal, ['header', 'X-Experience-API-Version']); +}; diff --git a/src/apps/agents/fetchAuthRepo/Config.ts b/src/apps/agents/fetchAuthRepo/Config.ts new file mode 100644 index 000000000..781f87d08 --- /dev/null +++ b/src/apps/agents/fetchAuthRepo/Config.ts @@ -0,0 +1,3 @@ +export default interface Config { + readonly llClientInfoEndpoint: string; +} diff --git a/src/apps/agents/fetchAuthRepo/getClient.ts b/src/apps/agents/fetchAuthRepo/getClient.ts new file mode 100644 index 000000000..59561dc3d --- /dev/null +++ b/src/apps/agents/fetchAuthRepo/getClient.ts @@ -0,0 +1,36 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import fetch from 'node-fetch'; +import ClientModel from '../models/ClientModel'; +import GetClientOptions from '../repoFactory/options/GetClientOptions'; +import GetClientResult from '../repoFactory/results/GetClientResult'; +import Config from './Config'; + +const OK_HTTP_CODE = 200; +const NO_MODEL_HTTP_CODE = 404; + +export default (config: Config) => { + return async (opts: GetClientOptions): Promise => { + const json = await fetch(config.llClientInfoEndpoint, { + headers: { + Authorization: opts.authToken, + }, + }).then((res) => { + if (res.status === NO_MODEL_HTTP_CODE) { + throw new NoModel('ClientModel'); + } + if (res.status !== OK_HTTP_CODE) { + throw new Error(`Getting client failed with error code ${res.status}`); + } + return res.json(); + }); + + const client: ClientModel = { + _id: json._id as string, + isTrusted: json.isTrusted as boolean, + lrs_id: json.lrs_id as string, + organisation: json.organisation as string, + scopes: json.scopes as string[], + }; + return { client }; + }; +}; diff --git a/src/apps/agents/fetchAuthRepo/index.ts b/src/apps/agents/fetchAuthRepo/index.ts new file mode 100644 index 000000000..fa66ea3e6 --- /dev/null +++ b/src/apps/agents/fetchAuthRepo/index.ts @@ -0,0 +1,9 @@ +import AuthRepo from '../repoFactory/AuthRepo'; +import Config from './Config'; +import getClient from './getClient'; + +export default (config: Config): AuthRepo => { + return { + getClient: getClient(config), + }; +}; diff --git a/src/apps/agents/googleStorageRepo/Config.ts b/src/apps/agents/googleStorageRepo/Config.ts new file mode 100644 index 000000000..0f87b732a --- /dev/null +++ b/src/apps/agents/googleStorageRepo/Config.ts @@ -0,0 +1,7 @@ +import { Storage } from '@google-cloud/storage'; + +export default interface Config { + readonly bucketName: string; + readonly storage: Storage; + readonly subFolder: string; +} diff --git a/src/apps/agents/googleStorageRepo/clearRepo.ts b/src/apps/agents/googleStorageRepo/clearRepo.ts new file mode 100644 index 000000000..8f1bd16f0 --- /dev/null +++ b/src/apps/agents/googleStorageRepo/clearRepo.ts @@ -0,0 +1,9 @@ +import Config from './Config'; + +export default (config: Config) => { + return async (): Promise => { + await config.storage.bucket(config.bucketName).deleteFiles({ + prefix: config.subFolder, + }); + }; +}; diff --git a/src/apps/agents/googleStorageRepo/deleteProfileContent.ts b/src/apps/agents/googleStorageRepo/deleteProfileContent.ts new file mode 100644 index 000000000..275203f1e --- /dev/null +++ b/src/apps/agents/googleStorageRepo/deleteProfileContent.ts @@ -0,0 +1,12 @@ +import DeleteProfileContentOptions from '../repoFactory/options/DeleteProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: DeleteProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + const file = config.storage.bucket(config.bucketName).file(filePath); + await file.delete(); + }; +}; diff --git a/src/apps/agents/googleStorageRepo/getProfileContent.ts b/src/apps/agents/googleStorageRepo/getProfileContent.ts new file mode 100644 index 000000000..0531b334e --- /dev/null +++ b/src/apps/agents/googleStorageRepo/getProfileContent.ts @@ -0,0 +1,14 @@ +import GetProfileContentOptions from '../repoFactory/options/GetProfileContentOptions'; +import GetProfileContentResult from '../repoFactory/results/GetProfileContentResult'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: GetProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + const file = config.storage.bucket(config.bucketName).file(filePath); + const content = file.createReadStream(); + return { content }; + }; +}; diff --git a/src/apps/agents/googleStorageRepo/index.ts b/src/apps/agents/googleStorageRepo/index.ts new file mode 100644 index 000000000..a4684cb0c --- /dev/null +++ b/src/apps/agents/googleStorageRepo/index.ts @@ -0,0 +1,17 @@ +import StorageRepo from '../repoFactory/StorageRepo'; +import clearRepo from './clearRepo'; +import Config from './Config'; +import deleteProfileContent from './deleteProfileContent'; +import getProfileContent from './getProfileContent'; +import storeProfileContent from './storeProfileContent'; + +export default (config: Config): StorageRepo => { + return { + clearRepo: clearRepo(config), + deleteProfileContent: deleteProfileContent(config), + getProfileContent: getProfileContent(config), + migrate: async () => Promise.resolve(), + rollback: async () => Promise.resolve(), + storeProfileContent: storeProfileContent(config), + }; +}; diff --git a/src/apps/agents/googleStorageRepo/storeProfileContent.ts b/src/apps/agents/googleStorageRepo/storeProfileContent.ts new file mode 100644 index 000000000..65eab6733 --- /dev/null +++ b/src/apps/agents/googleStorageRepo/storeProfileContent.ts @@ -0,0 +1,18 @@ +import StoreProfileContentOptions from '../repoFactory/options/StoreProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: StoreProfileContentOptions): Promise => { + return new Promise((resolve, reject) => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + const file = config.storage.bucket(config.bucketName).file(filePath); + const writeStream = file.createWriteStream(); + opts.content.pipe(writeStream); + writeStream.on('finish', resolve); + opts.content.on('error', reject); + writeStream.on('error', reject); + }); + }; +}; diff --git a/src/apps/agents/localStorageRepo/Config.ts b/src/apps/agents/localStorageRepo/Config.ts new file mode 100644 index 000000000..68b2bdddb --- /dev/null +++ b/src/apps/agents/localStorageRepo/Config.ts @@ -0,0 +1,3 @@ +import CommonConfig from 'jscommons/dist/fsRepo/Config'; + +export default CommonConfig; diff --git a/src/apps/agents/localStorageRepo/deleteProfileContent.ts b/src/apps/agents/localStorageRepo/deleteProfileContent.ts new file mode 100644 index 000000000..668a92c16 --- /dev/null +++ b/src/apps/agents/localStorageRepo/deleteProfileContent.ts @@ -0,0 +1,12 @@ +import * as fs from 'fs-extra'; +import DeleteProfileContentOptions from '../repoFactory/options/DeleteProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: DeleteProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.storageDir, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + await fs.unlink(filePath); + }; +}; diff --git a/src/apps/agents/localStorageRepo/getProfileContent.ts b/src/apps/agents/localStorageRepo/getProfileContent.ts new file mode 100644 index 000000000..8fd3f564d --- /dev/null +++ b/src/apps/agents/localStorageRepo/getProfileContent.ts @@ -0,0 +1,14 @@ +import * as fs from 'fs-extra'; +import GetProfileContentOptions from '../repoFactory/options/GetProfileContentOptions'; +import GetProfileContentResult from '../repoFactory/results/GetProfileContentResult'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: GetProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.storageDir, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + const content = fs.createReadStream(filePath); + return { content }; + }; +}; diff --git a/src/apps/agents/localStorageRepo/index.ts b/src/apps/agents/localStorageRepo/index.ts new file mode 100644 index 000000000..44a6e2f21 --- /dev/null +++ b/src/apps/agents/localStorageRepo/index.ts @@ -0,0 +1,15 @@ +import commonFsRepo from 'jscommons/dist/fsRepo'; +import StorageRepo from '../repoFactory/StorageRepo'; +import Config from './Config'; +import deleteProfileContent from './deleteProfileContent'; +import getProfileContent from './getProfileContent'; +import storeProfileContent from './storeProfileContent'; + +export default (config: Config): StorageRepo => { + return { + deleteProfileContent: deleteProfileContent(config), + getProfileContent: getProfileContent(config), + storeProfileContent: storeProfileContent(config), + ...commonFsRepo(config), + }; +}; diff --git a/src/apps/agents/localStorageRepo/storeProfileContent.ts b/src/apps/agents/localStorageRepo/storeProfileContent.ts new file mode 100644 index 000000000..cb4514b34 --- /dev/null +++ b/src/apps/agents/localStorageRepo/storeProfileContent.ts @@ -0,0 +1,27 @@ +import * as fs from 'fs-extra'; +import StoreProfileContentOptions from '../repoFactory/options/StoreProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: StoreProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.storageDir, lrs_id: opts.lrs_id }); + await fs.ensureDir(profileDir); + await new Promise((resolve, reject) => { + const filePath = `${profileDir}/${opts.key}`; + const writeStream = fs.createWriteStream(filePath); + opts.content.pipe(writeStream); + writeStream.on('finish', () => { + resolve(); + }); + opts.content.on('error', (err: any) => { + /* istanbul ignore next */ + reject(err); + }); + writeStream.on('error', (err: any) => { + /* istanbul ignore next */ + reject(err); + }); + }); + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/Config.ts b/src/apps/agents/memoryModelsRepo/Config.ts new file mode 100644 index 000000000..e7659af00 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/Config.ts @@ -0,0 +1,12 @@ +/* tslint:disable:readonly-keyword */ +import Profile from '../models/Profile'; + +export interface State { + agentProfiles: Profile[]; +} + +interface Config { + state: State; +} + +export default Config; diff --git a/src/apps/agents/memoryModelsRepo/deleteProfile.ts b/src/apps/agents/memoryModelsRepo/deleteProfile.ts new file mode 100644 index 000000000..6b6ef23df --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/deleteProfile.ts @@ -0,0 +1,51 @@ +/* tslint:disable:no-let */ +import NoModel from 'jscommons/dist/errors/NoModel'; +import IfMatch from '../errors/IfMatch'; +import DeleteProfileOptions from '../repoFactory/options/DeleteProfileOptions'; +import DeleteProfileResult from '../repoFactory/results/DeleteProfileResult'; +import Config from './Config'; +import matchProfileIdentifier from './utils/matchProfileIdentifier'; + +export default (config: Config) => { + return async (opts: DeleteProfileOptions): Promise => { + const storedProfiles = config.state.agentProfiles; + const client = opts.client; + const agent = opts.agent; + let existingId: string | undefined; + let existingContentType: string | undefined; + let existingExtension: string | undefined; + const remainingProfiles = storedProfiles.filter((profile) => { + const isMatch = ( + matchProfileIdentifier({ client, agent, profile }) && + profile.profileId === opts.profileId + ); + + if (isMatch) { + existingId = profile.id; + existingContentType = profile.contentType; + existingExtension = profile.extension; + + if (opts.ifMatch !== undefined && profile.etag !== opts.ifMatch) { + throw new IfMatch(); + } + } + + return !isMatch; + }); + + if ( + existingId !== undefined && + existingContentType !== undefined && + existingExtension !== undefined) { + config.state.agentProfiles = remainingProfiles; + return { + contentType: existingContentType, + extension: existingExtension, + id: existingId, + }; + } + + /* istanbul ignore next */ + throw new NoModel('Agent Profile'); + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/getProfile.ts b/src/apps/agents/memoryModelsRepo/getProfile.ts new file mode 100644 index 000000000..672e82ce1 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/getProfile.ts @@ -0,0 +1,27 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import GetProfileOptions from '../repoFactory/options/GetProfileOptions'; +import GetProfileResult from '../repoFactory/results/GetProfileResult'; +import Config from './Config'; +import matchProfileIdentifier from './utils/matchProfileIdentifier'; + +export default (config: Config) => { + return async (opts: GetProfileOptions): Promise => { + const client = opts.client; + const agent = opts.agent; + const matchingProfiles = config.state.agentProfiles.filter((profile) => { + return ( + matchProfileIdentifier({ client, agent, profile }) && + profile.profileId === opts.profileId + ); + }); + + const isExistingIfi = matchingProfiles.length !== 0; + if (!isExistingIfi) { + /* istanbul ignore next */ + throw new NoModel('Agent Profile'); + } + + const { id, content, contentType, updatedAt, etag, extension } = matchingProfiles[0]; + return { id, content, contentType, updatedAt, etag, extension }; + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/getProfiles.ts b/src/apps/agents/memoryModelsRepo/getProfiles.ts new file mode 100644 index 000000000..58838e7ab --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/getProfiles.ts @@ -0,0 +1,28 @@ +import Profile from '../models/Profile'; +import GetProfilesOptions from '../repoFactory/options/GetProfilesOptions'; +import GetProfilesResult from '../repoFactory/results/GetProfilesResult'; +import Config from './Config'; +import matchProfileIdentifier from './utils/matchProfileIdentifier'; + +const matchProfileSince = (profile: Profile, since?: Date) => { + return since === undefined ? true : profile.updatedAt > since; +}; + +export default (config: Config) => { + return async (opts: GetProfilesOptions): Promise => { + const client = opts.client; + const agent = opts.agent; + const matchingProfiles = config.state.agentProfiles.filter((profile) => { + return ( + matchProfileIdentifier({ client, agent, profile }) && + matchProfileSince(profile, opts.since) + ); + }); + + const profileIds = matchingProfiles.map((profile) => { + return profile.profileId; + }); + + return { profileIds }; + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/hasProfile.ts b/src/apps/agents/memoryModelsRepo/hasProfile.ts new file mode 100644 index 000000000..cc5a299b0 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/hasProfile.ts @@ -0,0 +1,15 @@ +import HasProfileOptions from '../repoFactory/options/HasProfileOptions'; +import HasProfileResult from '../repoFactory/results/HasProfileResult'; +import Config from './Config'; +import matchUniqueProfile from './utils/matchUniqueProfile'; + +export default (config: Config) => { + return async ({ client, agent, profileId }: HasProfileOptions): Promise => { + const matchingProfiles = config.state.agentProfiles.filter((profile) => { + return matchUniqueProfile({ client, agent, profile, profileId }); + }); + + const hasProfile = matchingProfiles.length !== 0; + return { hasProfile }; + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/index.ts b/src/apps/agents/memoryModelsRepo/index.ts new file mode 100644 index 000000000..5c67be628 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/index.ts @@ -0,0 +1,21 @@ +import commonMemoryRepo from 'jscommons/dist/memoryRepo'; +import ModelsRepo from '../repoFactory/ModelsRepo'; +import Config from './Config'; +import deleteProfile from './deleteProfile'; +import getProfile from './getProfile'; +import getProfiles from './getProfiles'; +import hasProfile from './hasProfile'; +import overwriteProfile from './overwriteProfile'; +import patchProfile from './patchProfile'; + +export default (config: Config): ModelsRepo => { + return { + deleteProfile: deleteProfile(config), + getProfile: getProfile(config), + getProfiles: getProfiles(config), + hasProfile: hasProfile(config), + overwriteProfile: overwriteProfile(config), + patchProfile: patchProfile(config), + ...commonMemoryRepo(config), + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/overwriteProfile.ts b/src/apps/agents/memoryModelsRepo/overwriteProfile.ts new file mode 100644 index 000000000..f53ac72fe --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/overwriteProfile.ts @@ -0,0 +1,48 @@ +/* tslint:disable:no-let */ +import OverwriteProfileOptions from '../repoFactory/options/OverwriteProfileOptions'; +import OverwriteProfileResult from '../repoFactory/results/OverwriteProfileResult'; +import Config from './Config'; +import checkEtag from './utils/checkEtag'; +import checkMaxEtags from './utils/checkMaxEtags'; +import createProfile from './utils/createProfile'; +import matchUniqueProfile from './utils/matchUniqueProfile'; + +export default (config: Config) => { + return async (opts: OverwriteProfileOptions): Promise => { + // Overwrites the content if the profile does already exist. + let existingId: string | undefined; + const { agent, profileId, client, ifMatch, ifNoneMatch } = opts; + checkMaxEtags(ifMatch, ifNoneMatch); + config.state.agentProfiles = config.state.agentProfiles.map((profile) => { + const isMatch = matchUniqueProfile({ client, agent, profile, profileId }); + + if (!isMatch) { + return profile; + } + + checkEtag({ profile, ifMatch, ifNoneMatch }); + + existingId = profile.id; + return { + ...profile, + + // Overwrites the content and contentType. + content: opts.content, + contentType: opts.contentType, + etag: opts.etag, + extension: opts.extension, + + // Updates updatedAt time. + updatedAt: new Date(), + }; + }); + + // Creates the Profile if the profile doesn't already exist. + if (existingId === undefined) { + const createdProfile = createProfile(config, opts); + return { id: createdProfile.id, extension: createdProfile.extension }; + } + + return { id: existingId, extension: '' }; + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/patchProfile.ts b/src/apps/agents/memoryModelsRepo/patchProfile.ts new file mode 100644 index 000000000..317ece017 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/patchProfile.ts @@ -0,0 +1,58 @@ +/* tslint:disable:no-let */ +import { isPlainObject } from 'lodash'; +import NonJsonObject from '../errors/NonJsonObject'; +import PatchProfileOptions from '../repoFactory/options/PatchProfileOptions'; +import { jsonContentType } from '../utils/constants'; +import Config from './Config'; +import checkEtag from './utils/checkEtag'; +import checkMaxEtags from './utils/checkMaxEtags'; +import createProfile from './utils/createProfile'; +import matchUniqueProfile from './utils/matchUniqueProfile'; + +export default (config: Config) => { + return async (opts: PatchProfileOptions): Promise => { + // Patches the content if the profile does already exist. + let isExistingProfile = false; + const { agent, profileId, client, ifMatch, ifNoneMatch } = opts; + checkMaxEtags(ifMatch, ifNoneMatch); + config.state.agentProfiles = config.state.agentProfiles.map((profile) => { + const isMatch = matchUniqueProfile({ client, agent, profile, profileId }); + const isJson = ( + isMatch && + profile.contentType === jsonContentType && + isPlainObject(profile.content) + ); + + if (!isMatch) { + return profile; + } + + checkEtag({ profile, ifMatch, ifNoneMatch }); + + isExistingProfile = true; + if (!isJson) { + throw new NonJsonObject(); + } + + return { + ...profile, + + // Merges top-level properties in content. + content: { + ...profile.content, + ...opts.content, + }, + etag: opts.etag, + extension: 'json', + + // Updates updatedAt time. + updatedAt: new Date(), + }; + }); + + // Creates the Profile if the profile doesn't already exist. + if (!isExistingProfile) { + createProfile(config, opts); + } + }; +}; diff --git a/src/apps/agents/memoryModelsRepo/utils/checkEtag.ts b/src/apps/agents/memoryModelsRepo/utils/checkEtag.ts new file mode 100644 index 000000000..343331cd8 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/utils/checkEtag.ts @@ -0,0 +1,19 @@ +import IfMatch from '../../errors/IfMatch'; +import IfNoneMatch from '../../errors/IfNoneMatch'; +import Profile from '../../models/Profile'; + +export interface Options { + readonly profile: Profile; + readonly ifMatch?: string; + readonly ifNoneMatch?: string; +} + +export default ({ profile, ifMatch, ifNoneMatch }: Options) => { + if (ifMatch !== undefined && profile.etag !== ifMatch) { + throw new IfMatch(); + } + + if (ifNoneMatch !== undefined && ifNoneMatch === '*') { + throw new IfNoneMatch(); + } +}; diff --git a/src/apps/agents/memoryModelsRepo/utils/checkMaxEtags.ts b/src/apps/agents/memoryModelsRepo/utils/checkMaxEtags.ts new file mode 100644 index 000000000..5afc60811 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/utils/checkMaxEtags.ts @@ -0,0 +1,7 @@ +import MaxEtags from '../../errors/MaxEtags'; + +export default (ifMatch?: string, ifNoneMatch?: string) => { + if (ifMatch !== undefined && ifNoneMatch !== undefined) { + throw new MaxEtags(); + } +}; diff --git a/src/apps/agents/memoryModelsRepo/utils/createProfile.ts b/src/apps/agents/memoryModelsRepo/utils/createProfile.ts new file mode 100644 index 000000000..eff7bb054 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/utils/createProfile.ts @@ -0,0 +1,35 @@ +import { v4 as uuid } from 'uuid'; +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; +import Profile from '../../models/Profile'; +import Config from '../Config'; + +export interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly content: any; + readonly contentType: string; + readonly etag: string; + readonly extension: string; + readonly profileId: string; +} + +export default (config: Config, opts: Options): Profile => { + const profile: Profile = { + agent: opts.agent, + content: opts.content, + contentType: opts.contentType, + etag: opts.etag, + extension: opts.extension, + id: uuid(), + lrs: opts.client.lrs_id, + organisation: opts.client.organisation, + profileId: opts.profileId, + updatedAt: new Date(), + }; + config.state.agentProfiles = [ + ...config.state.agentProfiles, + profile, + ]; + return profile; +}; diff --git a/src/apps/agents/memoryModelsRepo/utils/isMatchingAgent.ts b/src/apps/agents/memoryModelsRepo/utils/isMatchingAgent.ts new file mode 100644 index 000000000..fb30fe627 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/utils/isMatchingAgent.ts @@ -0,0 +1,22 @@ +import Agent from '../../models/Agent'; + +export default (storedAgent: Agent, agent: Agent) => { + if (agent.mbox !== undefined) { + return storedAgent.mbox === agent.mbox; + } + if (agent.mbox_sha1sum !== undefined) { + return storedAgent.mbox_sha1sum === agent.mbox_sha1sum; + } + if (agent.openid !== undefined) { + return storedAgent.openid === agent.openid; + } + if (agent.account !== undefined) { + return ( + storedAgent.account !== undefined && + storedAgent.account.homePage === agent.account.homePage && + storedAgent.account.name === agent.account.name + ); + } + /* istanbul ignore next */ + throw new Error('Invalid agent IFI'); +}; diff --git a/src/apps/agents/memoryModelsRepo/utils/matchProfileIdentifier.ts b/src/apps/agents/memoryModelsRepo/utils/matchProfileIdentifier.ts new file mode 100644 index 000000000..dddf0b72a --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/utils/matchProfileIdentifier.ts @@ -0,0 +1,18 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; +import Profile from '../../models/Profile'; +import isMatchingAgent from './isMatchingAgent'; + +export interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly profile: Profile; +} + +export default ({ client, agent, profile }: Options) => { + return ( + profile.organisation === client.organisation && + profile.lrs === client.lrs_id && + isMatchingAgent(profile.agent, agent) + ); +}; diff --git a/src/apps/agents/memoryModelsRepo/utils/matchUniqueProfile.ts b/src/apps/agents/memoryModelsRepo/utils/matchUniqueProfile.ts new file mode 100644 index 000000000..26273e844 --- /dev/null +++ b/src/apps/agents/memoryModelsRepo/utils/matchUniqueProfile.ts @@ -0,0 +1,18 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; +import Profile from '../../models/Profile'; +import matchProfileIdentifier from './matchProfileIdentifier'; + +export interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly profile: Profile; + readonly profileId: string; +} + +export default ({ client, agent, profile, profileId }: Options) => { + return ( + matchProfileIdentifier({ client, agent, profile }) && + profile.profileId === profileId + ); +}; diff --git a/src/apps/agents/models/Account.ts b/src/apps/agents/models/Account.ts new file mode 100644 index 000000000..0b944ce79 --- /dev/null +++ b/src/apps/agents/models/Account.ts @@ -0,0 +1,6 @@ +interface Model { + readonly homePage: string; + readonly name: string; +} + +export default Model; diff --git a/src/apps/agents/models/Agent.ts b/src/apps/agents/models/Agent.ts new file mode 100644 index 000000000..d1c6a8dab --- /dev/null +++ b/src/apps/agents/models/Agent.ts @@ -0,0 +1,10 @@ +import Account from './Account'; + +interface Model { + readonly mbox?: string; + readonly mbox_sha1sum?: string; + readonly openid?: string; + readonly account?: Account; +} + +export default Model; diff --git a/src/apps/agents/models/ClientModel.ts b/src/apps/agents/models/ClientModel.ts new file mode 100644 index 000000000..e2c2d906f --- /dev/null +++ b/src/apps/agents/models/ClientModel.ts @@ -0,0 +1,9 @@ +interface Model { + readonly _id: string; + readonly organisation: string; + readonly lrs_id: string; + readonly isTrusted: boolean; + readonly scopes: string[]; +} + +export default Model; diff --git a/src/apps/agents/models/Ifi.ts b/src/apps/agents/models/Ifi.ts new file mode 100644 index 000000000..76bf387b9 --- /dev/null +++ b/src/apps/agents/models/Ifi.ts @@ -0,0 +1,16 @@ +import Account from '../models/Account'; + +// Inverse function identifier +export interface StringIfi { + readonly key: 'mbox' | 'mbox_sha1sum' | 'openid'; + readonly value: string; +} + +export interface AccountIfi { + readonly key: 'account'; + readonly value: Account; +} + +type Ifi = StringIfi | AccountIfi; + +export default Ifi; diff --git a/src/apps/agents/models/Profile.ts b/src/apps/agents/models/Profile.ts new file mode 100644 index 000000000..ba34d21bb --- /dev/null +++ b/src/apps/agents/models/Profile.ts @@ -0,0 +1,16 @@ +import Agent from './Agent'; + +interface Model { + readonly etag: string; + readonly id: string; + readonly organisation: string; + readonly agent: Agent; + readonly profileId: string; + readonly extension: string; + readonly content?: any; + readonly contentType: string; + readonly lrs: string; + readonly updatedAt: Date; +} + +export default Model; diff --git a/src/apps/agents/mongoAuthRepo/Config.ts b/src/apps/agents/mongoAuthRepo/Config.ts new file mode 100644 index 000000000..d390644b4 --- /dev/null +++ b/src/apps/agents/mongoAuthRepo/Config.ts @@ -0,0 +1,7 @@ +import { Db } from 'mongodb'; + +interface Config { + readonly db: () => Promise; +} + +export default Config; diff --git a/src/apps/agents/mongoAuthRepo/getClient.ts b/src/apps/agents/mongoAuthRepo/getClient.ts new file mode 100644 index 000000000..776b3f17b --- /dev/null +++ b/src/apps/agents/mongoAuthRepo/getClient.ts @@ -0,0 +1,87 @@ +import atob from 'atob'; +import NoModel from 'jscommons/dist/errors/NoModel'; +import { Db } from 'mongodb'; +import ExpiredClientError from '../errors/ExpiredClientError'; +import UntrustedClientError from '../errors/UntrustedClientError'; +import ClientModel from '../models/ClientModel'; +import GetClientOptions from '../repoFactory/options/GetClientOptions'; +import GetClientResult from '../repoFactory/results/GetClientResult'; +import Config from './Config'; + +const findClientByAccessToken = async (db: Db, accessToken: string) => { + const accessTokenDoc = await db.collection('oAuthTokens').findOne({ + accessToken, + }); + if (!accessTokenDoc) { + return null; + } + const clientDoc = await db.collection('client').findOne({ + _id: accessTokenDoc.clientId, + }); + return clientDoc; +}; + +const findClientByBasicAuth = async (db: Db, encodedBasicAuthToken: string) => { + const decodedAuthToken = atob(encodedBasicAuthToken); + const splitAuthToken = decodedAuthToken.split(':'); + const [key, secret] = splitAuthToken; + const clientDoc = await db.collection('client').findOne({ + 'api.basic_key': key, + 'api.basic_secret': secret, + }); + return clientDoc; +}; + +const findClientWithAuth = async (db: Db, authToken: string) => { + const [authType, authValue] = authToken.split(' '); + switch (authType) { + case 'Basic': + return findClientByBasicAuth(db, authValue); + case 'Bearer': + return findClientByAccessToken(db, authValue); + default: + throw new NoModel('Client'); + } +}; + +export default (config: Config) => { + return async ({ authToken }: GetClientOptions): Promise => { + const db = await config.db(); + const clientDoc = await findClientWithAuth(db, authToken); + + if (clientDoc === null || clientDoc === undefined) { + throw new NoModel('Client'); + } + + if (clientDoc.isTrusted === false) { + throw new UntrustedClientError(); + } + + const [orgDoc, lrsDoc] = await Promise.all([ + db.collection('organisations').findOne({ + _id: clientDoc.organisation, + }), + db.collection('lrs').findOne({ + _id: clientDoc.lrs_id, + }), + ]); + + if (orgDoc === null || orgDoc === undefined || lrsDoc === null || lrsDoc === undefined) { + throw new NoModel('Client'); + } + + if (orgDoc.expiration !== null && orgDoc.expiration < new Date()) { + throw new ExpiredClientError(); + } + + const client: ClientModel = { + _id: clientDoc._id.toString() as string, + isTrusted: clientDoc.isTrusted as boolean, + lrs_id: clientDoc.lrs_id.toString() as string, + organisation: clientDoc.organisation.toString() as string, + scopes: clientDoc.scopes as string[], + }; + + return { client }; + }; +}; diff --git a/src/apps/agents/mongoAuthRepo/index.ts b/src/apps/agents/mongoAuthRepo/index.ts new file mode 100644 index 000000000..fa66ea3e6 --- /dev/null +++ b/src/apps/agents/mongoAuthRepo/index.ts @@ -0,0 +1,9 @@ +import AuthRepo from '../repoFactory/AuthRepo'; +import Config from './Config'; +import getClient from './getClient'; + +export default (config: Config): AuthRepo => { + return { + getClient: getClient(config), + }; +}; diff --git a/src/apps/agents/mongoAuthRepo/tests/getClient.test.ts b/src/apps/agents/mongoAuthRepo/tests/getClient.test.ts new file mode 100644 index 000000000..c5dd86ee7 --- /dev/null +++ b/src/apps/agents/mongoAuthRepo/tests/getClient.test.ts @@ -0,0 +1,161 @@ +// tslint:disable:max-file-line-count +import * as assert from 'assert'; +import btoa from 'btoa'; +import NoModel from 'jscommons/dist/errors/NoModel'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { ObjectID } from 'mongodb'; +import connectToMongoDb from '../../../../utils/connectToMongoDb'; +import ExpiredClientError from '../../errors/ExpiredClientError'; +import UntrustedClientError from '../../errors/UntrustedClientError'; +import mongoFactory from '../index'; + +const TEST_BASIC_KEY = '123'; +const TEST_BASIC_SECRET = 'abc'; +const TEST_TOKEN = `Basic ${btoa(`${TEST_BASIC_KEY}:${TEST_BASIC_SECRET}`)}`; +const TEST_ACCESS_TOKEN = '11112222-3333-4444-5555-666677778888'; +const TEST_CLIENT = { + _id: new ObjectID('5988f0f00000000000000123'), + api: { + basic_key: TEST_BASIC_KEY, + basic_secret: TEST_BASIC_SECRET, + }, + authority: JSON.stringify({ + mbox: 'mailto:authority@example.com', + objectType: 'Agent', + }), + lrs_id: new ObjectID('5988f0f00000000000000001'), + organisation: new ObjectID('5988f0f00000000000000000'), +}; +const TEST_ORG = { + _id: new ObjectID('5988f0f00000000000000000'), + createdAt: new Date('2017-10-25T14:39:44.962Z'), + name: 'Test Org', + updatedAt: new Date('2017-10-25T14:39:58.376Z'), +}; +const TEST_STORE = { + _id: new ObjectID('5988f0f00000000000000001'), + createdAt: new Date('2017-10-25T14:39:44.962Z'), + description: 'Test LRS Description', + organisation: new ObjectID('5988f0f00000000000000000'), + statementCount: 0, + title: 'Test LRS', + updatedAt: new Date('2017-10-25T14:39:58.376Z'), +}; +const TEST_OAUTH_TOKEN = { + _id: new ObjectID('5988f0f00000000000000002'), + clientId: new ObjectID('5988f0f00000000000000123'), + accessToken: TEST_ACCESS_TOKEN, + createdAt: new Date('2017-10-25T14:39:44.962Z'), + expireAt: new Date('2017-10-25T15:39:44.962Z'), +}; + +describe('getClient from mongo client', () => { + const connection = connectToMongoDb(); + const authRepo = mongoFactory({ db: connection }); + + beforeEach(async () => { + const db = await connection(); + await db.dropDatabase(); + }); + + it('should return a client from the db', async () => { + const db = await connection(); + await db.collection('organisations').insertOne(TEST_ORG); + await db.collection('lrs').insertOne(TEST_STORE); + await db.collection('client').insertOne(TEST_CLIENT); + const result = await authRepo.getClient({ authToken: TEST_TOKEN }); + assert.equal(result.client._id, TEST_CLIENT._id); + }); + + it('should error when getting without any clients in the DB', async () => { + const promise = authRepo.getClient({ authToken: TEST_TOKEN }); + await assertError(NoModel, promise); + }); + + it('should error when getting a untrusted client', async () => { + const db = await connection(); + await db.collection('client').insertOne({ + ...TEST_CLIENT, + isTrusted: false, + }); + const promise = authRepo.getClient({ authToken: TEST_TOKEN }); + await assertError(UntrustedClientError, promise); + }); + + it('should error when getting a client with a missing store', async () => { + const db = await connection(); + await db.collection('organisations').insertOne(TEST_ORG); + await db.collection('client').insertOne(TEST_CLIENT); + const promise = authRepo.getClient({ authToken: TEST_TOKEN }); + await assertError(NoModel, promise); + }); + + it('should error when getting a client with a missing org', async () => { + const db = await connection(); + await db.collection('lrs').insertOne(TEST_STORE); + await db.collection('client').insertOne(TEST_CLIENT); + const promise = authRepo.getClient({ authToken: TEST_TOKEN }); + await assertError(NoModel, promise); + }); + + it('should error when getting a client with an expired org', async () => { + const db = await connection(); + await db.collection('organisations').insertOne({ + ...TEST_ORG, + expiration: new Date(), + }); + await db.collection('lrs').insertOne(TEST_STORE); + await db.collection('client').insertOne(TEST_CLIENT); + const promise = authRepo.getClient({ authToken: TEST_TOKEN }); + await assertError(ExpiredClientError, promise); + }); + + it('should not error when getting a client with an renewed org', async () => { + const db = await connection(); + await db.collection('organisations').insertOne({ + ...TEST_ORG, + expiration: null, + }); + await db.collection('lrs').insertOne(TEST_STORE); + await db.collection('client').insertOne(TEST_CLIENT); + await authRepo.getClient({ authToken: TEST_TOKEN }); + }); + + it('should return a client from the db when access_token is valid', async () => { + const db = await connection(); + await db.collection('organisations').insertOne({ + ...TEST_ORG, + expiration: null, + }); + await db.collection('lrs').insertOne(TEST_STORE); + await db.collection('client').insertOne(TEST_CLIENT); + await db.collection('oAuthTokens').insertOne(TEST_OAUTH_TOKEN); + const result = await authRepo.getClient({ authToken: `Bearer ${TEST_ACCESS_TOKEN}` }); + assert.equal(result.client._id, TEST_CLIENT._id); + }); + + it('should error when access_token is not found in collection', async () => { + const db = await connection(); + await db.collection('organisations').insertOne({ + ...TEST_ORG, + expiration: null, + }); + await db.collection('lrs').insertOne(TEST_STORE); + await db.collection('client').insertOne(TEST_CLIENT); + const promise = authRepo.getClient({ authToken: `Bearer ${TEST_ACCESS_TOKEN}` }); + await assertError(NoModel, promise); + }); + + it('should error when authToken starts from an invalid string', async () => { + const db = await connection(); + await db.collection('organisations').insertOne({ + ...TEST_ORG, + expiration: null, + }); + await db.collection('lrs').insertOne(TEST_STORE); + await db.collection('client').insertOne(TEST_CLIENT); + await db.collection('oAuthTokens').insertOne(TEST_OAUTH_TOKEN); + const promise = authRepo.getClient({ authToken: `Test ${TEST_ACCESS_TOKEN}` }); + await assertError(NoModel, promise); + }); +}); diff --git a/src/apps/agents/mongoModelsRepo/Config.ts b/src/apps/agents/mongoModelsRepo/Config.ts new file mode 100644 index 000000000..d390644b4 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/Config.ts @@ -0,0 +1,7 @@ +import { Db } from 'mongodb'; + +interface Config { + readonly db: () => Promise; +} + +export default Config; diff --git a/src/apps/agents/mongoModelsRepo/deleteProfile.ts b/src/apps/agents/mongoModelsRepo/deleteProfile.ts new file mode 100644 index 000000000..c2a61884d --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/deleteProfile.ts @@ -0,0 +1,48 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import IfMatch from '../errors/IfMatch'; +import DeleteProfileOptions from '../repoFactory/options/DeleteProfileOptions'; +import DeleteProfileResult from '../repoFactory/results/DeleteProfileResult'; +import Config from './Config'; +import { COLLECTION_NAME } from './utils/constants'; +import getEtagFilter from './utils/getEtagFilter'; +import getProfileFilter from './utils/getProfileFilter'; + +// Within this code, Etags (ifMatch/ifNoneMatch) are used to manage concurrent creates/updates. +// Docs: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#concurrency + +export default (config: Config) => { + return async (opts: DeleteProfileOptions): Promise => { + const collection = (await config.db()).collection(COLLECTION_NAME); + const etagFilter = getEtagFilter(opts.ifMatch); + const profileFilter = getProfileFilter(opts); + + // Deletes the document if it matches the profile and etag filters. + const opResult = await collection.findOneAndDelete({ + ...profileFilter, + ...etagFilter, + }, {}); + + // Determines if the identifier was deleted. + const matchedDocuments = opResult.lastErrorObject.n as number; + const wasDeleted = matchedDocuments === 1; + + // Returns the result of the deletion if the document was deleted. + if (wasDeleted) { + const deletedDoc = opResult.value; + return { + contentType: deletedDoc.contentType, + extension: deletedDoc.extension, + id: deletedDoc._id.toString(), + }; + } + + // Attempts to find document without the ETag filter to determine if there was an ETag error. + const foundDoc = await collection.findOne(profileFilter, {}); + if (foundDoc !== null && foundDoc !== undefined) { + throw new IfMatch(); + } + + /* istanbul ignore next */ + throw new NoModel('Agent Profile'); + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/getProfile.ts b/src/apps/agents/mongoModelsRepo/getProfile.ts new file mode 100644 index 000000000..08fc201f1 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/getProfile.ts @@ -0,0 +1,29 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import { defaultTo } from 'lodash'; +import GetProfileOptions from '../repoFactory/options/GetProfileOptions'; +import GetProfileResult from '../repoFactory/results/GetProfileResult'; +import Config from './Config'; +import { COLLECTION_NAME } from './utils/constants'; +import getProfileFilter from './utils/getProfileFilter'; + +export default (config: Config) => { + return async (opts: GetProfileOptions): Promise => { + const collection = (await config.db()).collection(COLLECTION_NAME); + const filter = getProfileFilter(opts); + const document = await collection.findOne(filter); + + if (document === null || document === undefined) { + /* istanbul ignore next */ + throw new NoModel('Agent Profile'); + } + + return { + content: defaultTo(document.content, undefined), + contentType: document.contentType, + etag: document.etag, + extension: document.extension, + id: document._id.toString(), + updatedAt: document.updatedAt, + }; + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/getProfiles.ts b/src/apps/agents/mongoModelsRepo/getProfiles.ts new file mode 100644 index 000000000..d63f43ee6 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/getProfiles.ts @@ -0,0 +1,22 @@ +/* tslint:disable:deprecation - find isn't really deprecated */ +import GetProfilesOptions from '../repoFactory/options/GetProfilesOptions'; +import GetProfilesResult from '../repoFactory/results/GetProfilesResult'; +import Config from './Config'; +import { COLLECTION_NAME } from './utils/constants'; +import getProfilesFilter from './utils/getProfilesFilter'; +import getSinceFilter from './utils/getSinceFilter'; + +export default (config: Config) => { + return async (opts: GetProfilesOptions): Promise => { + const collection = (await config.db()).collection(COLLECTION_NAME); + const filter = { + ...getProfilesFilter(opts), + ...getSinceFilter(opts.since), + }; + const documents = await collection.find(filter).project({ profileId: 1 }).toArray(); + const profileIds = documents.map((document) => { + return document.profileId; + }); + return { profileIds }; + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/hasProfile.ts b/src/apps/agents/mongoModelsRepo/hasProfile.ts new file mode 100644 index 000000000..3d4f34fe9 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/hasProfile.ts @@ -0,0 +1,15 @@ +import HasProfileOptions from '../repoFactory/options/HasProfileOptions'; +import HasProfileResult from '../repoFactory/results/HasProfileResult'; +import Config from './Config'; +import { COLLECTION_NAME } from './utils/constants'; +import getProfileFilter from './utils/getProfileFilter'; + +export default (config: Config) => { + return async (opts: HasProfileOptions): Promise => { + const collection = (await config.db()).collection(COLLECTION_NAME); + const filter = getProfileFilter(opts); + const document = await collection.findOne(filter, { fields: { _id: 0 } }); + const hasProfile = document !== null && document !== undefined; + return { hasProfile }; + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/index.ts b/src/apps/agents/mongoModelsRepo/index.ts new file mode 100644 index 000000000..9f0cd0fcc --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/index.ts @@ -0,0 +1,24 @@ +import ModelsRepo from '../repoFactory/ModelsRepo'; +import Config from './Config'; +import deleteProfile from './deleteProfile'; +import getProfile from './getProfile'; +import getProfiles from './getProfiles'; +import hasProfile from './hasProfile'; +import overwriteProfile from './overwriteProfile'; +import patchProfile from './patchProfile'; + +export default (config: Config): ModelsRepo => { + return { + clearRepo: async () => { + await (await config.db()).dropDatabase(); + }, + deleteProfile: deleteProfile(config), + getProfile: getProfile(config), + getProfiles: getProfiles(config), + hasProfile: hasProfile(config), + migrate: async () => Promise.resolve(), + overwriteProfile: overwriteProfile(config), + patchProfile: patchProfile(config), + rollback: async () => Promise.resolve(), + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/overwriteProfile.ts b/src/apps/agents/mongoModelsRepo/overwriteProfile.ts new file mode 100644 index 000000000..dc28cc5e8 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/overwriteProfile.ts @@ -0,0 +1,90 @@ +/* tslint:disable:max-file-line-count */ +import { isPlainObject } from 'lodash'; +import IfMatch from '../errors/IfMatch'; +import IfNoneMatch from '../errors/IfNoneMatch'; +import MaxEtags from '../errors/MaxEtags'; +import OverwriteProfileOptions from '../repoFactory/options/OverwriteProfileOptions'; +import OverwriteProfileResult from '../repoFactory/results/OverwriteProfileResult'; +import Config from './Config'; +import { COLLECTION_NAME } from './utils/constants'; +import getEtagFilter from './utils/getEtagFilter'; +import getProfileFilter from './utils/getProfileFilter'; + +// Within this code, Etags (ifMatch/ifNoneMatch) are used to manage concurrent creates/updates. +// Docs: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#concurrency + +export default (config: Config) => { + return async (opts: OverwriteProfileOptions): Promise => { + const collection = (await config.db()).collection(COLLECTION_NAME); + const checkIfMatch = opts.ifMatch !== undefined; + const checkIfNoneMatch = opts.ifNoneMatch === '*'; + + if (checkIfMatch && checkIfNoneMatch) { + throw new MaxEtags(); + } + + const profileFilter = getProfileFilter(opts); + const update = { + // Overwrites the content and contentType. + content: opts.content, + contentType: opts.contentType, + etag: opts.etag, + extension: opts.extension, + isObjectContent: isPlainObject(opts.content), + + // Updates updatedAt time. + updatedAt: new Date(), + }; + + // Attempts to update the profile because the ifMatch option is provided. + if (checkIfMatch) { + const ifMatchFilter = getEtagFilter(opts.ifMatch); + + // Updates the profile if it exists with the correct ETag. + const updateOpResult = await collection.findOneAndUpdate({ + ...ifMatchFilter, + ...profileFilter, + }, { + $set: update, + }, { + returnOriginal: false, + upsert: false, + }); + + // Determines if the Profile was updated. + const updatedDocuments = updateOpResult.lastErrorObject.n as number; + if (updatedDocuments === 1) { + return { + extension: updateOpResult.value.extension, + id: updateOpResult.value._id.toString(), + }; + } + } + + // Creates the profile if it doesn't already exist. + const createOpResult = await collection.findOneAndUpdate(profileFilter, { + $setOnInsert: update, + }, { + returnOriginal: false, + upsert: true, + }); + + // Determines if the Profile was created or found. + const wasCreated = createOpResult.lastErrorObject.upserted !== undefined; + + // Throws the IfMatch error when the profile already exists. + // This is because there must have been an ETag mismatch in the previous update. + if (!wasCreated && checkIfMatch) { + throw new IfMatch(); + } + + if (!wasCreated && checkIfNoneMatch) { + throw new IfNoneMatch(); + } + + return { + extension: createOpResult.value.extension, + id: createOpResult.value._id.toString(), + }; + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/patchProfile.ts b/src/apps/agents/mongoModelsRepo/patchProfile.ts new file mode 100644 index 000000000..9cd3d73a2 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/patchProfile.ts @@ -0,0 +1,103 @@ +/* tslint:disable:max-file-line-count */ +import { mapKeys } from 'lodash'; +import IfMatch from '../errors/IfMatch'; +import IfNoneMatch from '../errors/IfNoneMatch'; +import MaxEtags from '../errors/MaxEtags'; +import NonJsonObject from '../errors/NonJsonObject'; +import PatchProfileOptions from '../repoFactory/options/PatchProfileOptions'; +import { jsonContentType } from '../utils/constants'; +import Config from './Config'; +import { COLLECTION_NAME, JSON_OBJECT_FILTER } from './utils/constants'; +import getEtagFilter from './utils/getEtagFilter'; +import getProfileFilter from './utils/getProfileFilter'; + +// Within this code, Etags (ifMatch/ifNoneMatch) are used to manage concurrent creates/updates. +// Docs: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#concurrency + +export default (config: Config) => { + return async (opts: PatchProfileOptions): Promise => { + const collection = (await config.db()).collection(COLLECTION_NAME); + const checkIfMatch = opts.ifMatch !== undefined; + const checkIfNoneMatch = opts.ifNoneMatch === '*'; + + if (checkIfMatch && checkIfNoneMatch) { + throw new MaxEtags(); + } + + const profileFilter = getProfileFilter(opts); + + // Ensures that the content is patched and not overwritten. + const contentPatch = mapKeys(opts.content, (_value, key) => { + return `content.${key}`; + }); + + const update = { + // Overwrites the content and contentType. + contentType: jsonContentType, + etag: opts.etag, + extension: 'json', + isObjectContent: true, + + // Updates updatedAt time. + updatedAt: new Date(), + }; + + // Attempts to patch the profile because the ifNoneMatch option isn't provided. + if (!checkIfNoneMatch) { + const ifMatchFilter = getEtagFilter(opts.ifMatch); + + // Updates the profile if it exists with JSON object content and the correct ETag. + const updateOpResult = await collection.findOneAndUpdate({ + ...ifMatchFilter, + ...JSON_OBJECT_FILTER, + ...profileFilter, + }, { + $set: { + ...contentPatch, + ...update, + }, + }, { + returnOriginal: false, // Ensures the updated document is returned. + upsert: false, // Does not create the profile when it doesn't exist. + }); + + // Determines if the Profile was updated. + const updatedDocuments = updateOpResult.lastErrorObject.n as number; + if (updatedDocuments === 1) { + return; + } + } + + // Creates the profile if it doesn't already exist. + const createOpResult = await collection.findOneAndUpdate(profileFilter, { + $setOnInsert: { + content: opts.content, + ...update, + }, + }, { + returnOriginal: false, // Ensures the updated document is returned. + upsert: true, // Creates the profile when it's not found. + }); + + // Determines if the Profile was created or found. + const wasCreated = createOpResult.lastErrorObject.upserted !== undefined; + + // When the profile is found at the create stage but not the update stage, + // And the ifNoneMatch option was not provided. + // Then the exsting profile either has the wrong content or didn't match the ifMatch option. + if (!wasCreated && !checkIfNoneMatch) { + if (checkIfMatch && createOpResult.value.etag !== opts.ifMatch) { + throw new IfMatch(); + } + throw new NonJsonObject(); + } + + // When the ifNoneMatch option is provided. + // No profile should be created when it already exists, hence we throw this error. + if (!wasCreated && checkIfNoneMatch) { + throw new IfNoneMatch(); + } + + return; + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/utils/constants.ts b/src/apps/agents/mongoModelsRepo/utils/constants.ts new file mode 100644 index 000000000..0ed1fdfb2 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/utils/constants.ts @@ -0,0 +1,7 @@ +import { jsonContentType } from '../../utils/constants'; + +export const COLLECTION_NAME = 'agentProfiles'; +export const JSON_OBJECT_FILTER = { + contentType: jsonContentType, + isObjectContent: true, +}; diff --git a/src/apps/agents/mongoModelsRepo/utils/getAgentFilter.ts b/src/apps/agents/mongoModelsRepo/utils/getAgentFilter.ts new file mode 100644 index 000000000..0c1431c5d --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/utils/getAgentFilter.ts @@ -0,0 +1,21 @@ +import Agent from '../../models/Agent'; + +export default (agent: Agent) => { + if (agent.mbox !== undefined) { + return { 'agent.mbox': agent.mbox }; + } + if (agent.mbox_sha1sum !== undefined) { + return { 'agent.mbox_sha1sum': agent.mbox_sha1sum }; + } + if (agent.openid !== undefined) { + return { 'agent.openid': agent.openid }; + } + if (agent.account !== undefined) { + return { + 'agent.account.homePage': agent.account.homePage, + 'agent.account.name': agent.account.name, + }; + } + /* istanbul ignore next */ + throw new Error('Invalid agent IFI'); +}; diff --git a/src/apps/agents/mongoModelsRepo/utils/getEtagFilter.ts b/src/apps/agents/mongoModelsRepo/utils/getEtagFilter.ts new file mode 100644 index 000000000..023a0c07a --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/utils/getEtagFilter.ts @@ -0,0 +1,6 @@ +export default (etag?: string) => { + if (etag === undefined) { + return {}; + } + return { etag }; +}; diff --git a/src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts b/src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts new file mode 100644 index 000000000..5d380b5d5 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts @@ -0,0 +1,17 @@ +import { ObjectID } from 'mongodb'; /* tslint:disable-line:no-unused */ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; +import getProfilesFilter from './getProfilesFilter'; + +export interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly profileId: string; +} + +export default (opts: Options) => { + return { + ...getProfilesFilter(opts), + profileId: opts.profileId, + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/utils/getProfilesFilter.ts b/src/apps/agents/mongoModelsRepo/utils/getProfilesFilter.ts new file mode 100644 index 000000000..0026ebb55 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/utils/getProfilesFilter.ts @@ -0,0 +1,17 @@ +import { ObjectID } from 'mongodb'; +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; +import getAgentFilter from './getAgentFilter'; + +export interface Options { + readonly agent: Agent; + readonly client: ClientModel; +} + +export default (opts: Options) => { + return { + ...getAgentFilter(opts.agent), + lrs: new ObjectID(opts.client.lrs_id), + organisation: new ObjectID(opts.client.organisation), + }; +}; diff --git a/src/apps/agents/mongoModelsRepo/utils/getSinceFilter.ts b/src/apps/agents/mongoModelsRepo/utils/getSinceFilter.ts new file mode 100644 index 000000000..4db8cdd24 --- /dev/null +++ b/src/apps/agents/mongoModelsRepo/utils/getSinceFilter.ts @@ -0,0 +1,6 @@ +export default (since?: Date) => { + if (since === undefined) { + return {}; + } + return { updatedAt: { $gt: since } }; +}; diff --git a/src/apps/agents/repo/auth/factory.ts b/src/apps/agents/repo/auth/factory.ts index 8c0732778..e6a2de5b4 100644 --- a/src/apps/agents/repo/auth/factory.ts +++ b/src/apps/agents/repo/auth/factory.ts @@ -1,6 +1,6 @@ -import mongoAuthRepo from '@learninglocker/xapi-agents/dist/mongoAuthRepo'; -import Repo from '@learninglocker/xapi-agents/dist/repoFactory/AuthRepo'; -import testAuthRepo from '@learninglocker/xapi-agents/dist/testAuthRepo'; +import mongoAuthRepo from '../../mongoAuthRepo'; +import Repo from '../../repoFactory/AuthRepo'; +import testAuthRepo from '../../testAuthRepo'; import FactoryConfig from './FactoryConfig'; export default (factoryConfig: FactoryConfig): Repo => { diff --git a/src/apps/agents/repo/factory.ts b/src/apps/agents/repo/factory.ts index 490ded0e1..9944ff235 100644 --- a/src/apps/agents/repo/factory.ts +++ b/src/apps/agents/repo/factory.ts @@ -1,4 +1,4 @@ -import Repo from '@learninglocker/xapi-agents/dist/repoFactory/Repo'; +import Repo from '../repoFactory/Repo'; import authFactory from './auth/factory'; import FactoryConfig from './FactoryConfig'; import modelsFactory from './models/factory'; diff --git a/src/apps/agents/repo/models/FactoryConfig.ts b/src/apps/agents/repo/models/FactoryConfig.ts index 1787413aa..95183050e 100644 --- a/src/apps/agents/repo/models/FactoryConfig.ts +++ b/src/apps/agents/repo/models/FactoryConfig.ts @@ -1,5 +1,5 @@ -import AgentProfile from '@learninglocker/xapi-agents/dist/models/Profile'; import { Db } from 'mongodb'; +import AgentProfile from '../../models/Profile'; export default interface FactoryConfig { readonly factoryName: string; diff --git a/src/apps/agents/repo/models/factory.ts b/src/apps/agents/repo/models/factory.ts index e4fd935f6..3f03e312f 100644 --- a/src/apps/agents/repo/models/factory.ts +++ b/src/apps/agents/repo/models/factory.ts @@ -1,6 +1,6 @@ -import memoryModelsRepo from '@learninglocker/xapi-agents/dist/memoryModelsRepo'; -import mongoModelsRepo from '@learninglocker/xapi-agents/dist/mongoModelsRepo'; -import Repo from '@learninglocker/xapi-agents/dist/repoFactory/ModelsRepo'; +import memoryModelsRepo from '../../memoryModelsRepo'; +import mongoModelsRepo from '../../mongoModelsRepo'; +import Repo from '../../repoFactory/ModelsRepo'; import FactoryConfig from './FactoryConfig'; export default (factoryConfig: FactoryConfig): Repo => { diff --git a/src/apps/agents/repo/storage/factory.ts b/src/apps/agents/repo/storage/factory.ts index 186bf4054..5087140b5 100644 --- a/src/apps/agents/repo/storage/factory.ts +++ b/src/apps/agents/repo/storage/factory.ts @@ -5,12 +5,12 @@ import { StorageURL, } from '@azure/storage-blob'; import Storage from '@google-cloud/storage'; -import azureStorageRepo from '@learninglocker/xapi-agents/dist/azureStorageRepo'; -import googleStorageRepo from '@learninglocker/xapi-agents/dist/googleStorageRepo'; -import localStorageRepo from '@learninglocker/xapi-agents/dist/localStorageRepo'; -import Repo from '@learninglocker/xapi-agents/dist/repoFactory/StorageRepo'; -import s3StorageRepo from '@learninglocker/xapi-agents/dist/s3StorageRepo'; -import { S3 } from 'aws-sdk'; +import S3 from 'aws-sdk/clients/s3'; +import azureStorageRepo from '../../azureStorageRepo'; +import googleStorageRepo from '../../googleStorageRepo'; +import localStorageRepo from '../../localStorageRepo'; +import Repo from '../../repoFactory/StorageRepo'; +import s3StorageRepo from '../../s3StorageRepo'; import FactoryConfig from './FactoryConfig'; export default (factoryConfig: FactoryConfig): Repo => { @@ -18,7 +18,7 @@ export default (factoryConfig: FactoryConfig): Repo => { case 's3': return s3StorageRepo({ bucketName: factoryConfig.s3.bucketName, - client: new S3(factoryConfig.s3.awsConfig), + client: new S3(factoryConfig.s3.awsConfig) as any, subFolder: factoryConfig.s3.subFolder, }); case 'google': diff --git a/src/apps/agents/repoFactory/AuthRepo.ts b/src/apps/agents/repoFactory/AuthRepo.ts new file mode 100644 index 000000000..728291f76 --- /dev/null +++ b/src/apps/agents/repoFactory/AuthRepo.ts @@ -0,0 +1,8 @@ +import GetClientOptions from './options/GetClientOptions'; +import GetClientResult from './results/GetClientResult'; + +interface Repo { + readonly getClient: (opts: GetClientOptions) => Promise; +} + +export default Repo; diff --git a/src/apps/agents/repoFactory/ModelsRepo.ts b/src/apps/agents/repoFactory/ModelsRepo.ts new file mode 100644 index 000000000..72b03d3c3 --- /dev/null +++ b/src/apps/agents/repoFactory/ModelsRepo.ts @@ -0,0 +1,23 @@ +import CommonRepo from 'jscommons/dist/repoFactory/Repo'; +import DeleteProfileOptions from './options/DeleteProfileOptions'; +import GetProfileOptions from './options/GetProfileOptions'; +import GetProfilesOptions from './options/GetProfilesOptions'; +import HasProfileOptions from './options/HasProfileOptions'; +import OverwriteProfileOptions from './options/OverwriteProfileOptions'; +import PatchProfileOptions from './options/PatchProfileOptions'; +import DeleteProfileResult from './results/DeleteProfileResult'; +import GetProfileResult from './results/GetProfileResult'; +import GetProfilesResult from './results/GetProfilesResult'; +import HasProfileResult from './results/HasProfileResult'; +import OverwriteProfileResult from './results/OverwriteProfileResult'; + +interface Repo extends CommonRepo { + readonly deleteProfile: (opts: DeleteProfileOptions) => Promise; + readonly getProfile: (opts: GetProfileOptions) => Promise; + readonly getProfiles: (opts: GetProfilesOptions) => Promise; + readonly hasProfile: (opts: HasProfileOptions) => Promise; + readonly overwriteProfile: (opts: OverwriteProfileOptions) => Promise; + readonly patchProfile: (opts: PatchProfileOptions) => Promise; +} + +export default Repo; diff --git a/src/apps/agents/repoFactory/Repo.ts b/src/apps/agents/repoFactory/Repo.ts new file mode 100644 index 000000000..28f30c487 --- /dev/null +++ b/src/apps/agents/repoFactory/Repo.ts @@ -0,0 +1,7 @@ +import AuthRepo from './AuthRepo'; +import ModelsRepo from './ModelsRepo'; +import StorageRepo from './StorageRepo'; + +interface Repo extends AuthRepo, ModelsRepo, StorageRepo {} + +export default Repo; diff --git a/src/apps/agents/repoFactory/StorageRepo.ts b/src/apps/agents/repoFactory/StorageRepo.ts new file mode 100644 index 000000000..07cb9894c --- /dev/null +++ b/src/apps/agents/repoFactory/StorageRepo.ts @@ -0,0 +1,13 @@ +import CommonRepo from 'jscommons/dist/repoFactory/Repo'; +import DeleteProfileContentOptions from './options/DeleteProfileContentOptions'; +import GetProfileContentOptions from './options/GetProfileContentOptions'; +import StoreProfileContentOptions from './options/StoreProfileContentOptions'; +import GetProfileContentResult from './results/GetProfileContentResult'; + +interface Repo extends CommonRepo { + readonly deleteProfileContent: (opts: DeleteProfileContentOptions) => Promise; + readonly getProfileContent: (opts: GetProfileContentOptions) => Promise; + readonly storeProfileContent: (opts: StoreProfileContentOptions) => Promise; +} + +export default Repo; diff --git a/src/apps/agents/repoFactory/index.ts b/src/apps/agents/repoFactory/index.ts new file mode 100644 index 000000000..5088a895a --- /dev/null +++ b/src/apps/agents/repoFactory/index.ts @@ -0,0 +1,130 @@ +// tslint:disable:max-file-line-count +import { + ContainerURL, ServiceURL, SharedKeyCredential, StorageURL, +} from '@azure/storage-blob'; +import Storage from '@google-cloud/storage'; +import S3 from 'aws-sdk/clients/s3'; +import connectToDb from 'jscommons/dist/mongoRepo/utils/connectToDb'; +import config from '../../../config'; +import logger from '../../../logger'; +import azureStorageRepo from '../azureStorageRepo'; +import fetchAuthRepo from '../fetchAuthRepo'; +import googleStorageRepo from '../googleStorageRepo'; +import localStorageRepo from '../localStorageRepo'; +import memoryModelsRepo from '../memoryModelsRepo'; +import Profile from '../models/Profile'; +import mongoAuthRepo from '../mongoAuthRepo'; +import mongoModelsRepo from '../mongoModelsRepo'; +import s3StorageRepo from '../s3StorageRepo'; +import testAuthRepo from '../testAuthRepo'; +import AuthRepo from './AuthRepo'; +import ModelsRepo from './ModelsRepo'; +import Repo from './Repo'; +import StorageRepo from './StorageRepo'; + +/* istanbul ignore next */ +const getAuthRepo = (): AuthRepo => { + switch (config.repoFactory.authRepoName) { + case 'test': + return testAuthRepo({}); + case 'fetch': + return fetchAuthRepo({ + llClientInfoEndpoint: config.fetchAuthRepo.llClientInfoEndpoint, + }); + default: case 'mongo': + return mongoAuthRepo({ + db: connectToDb({ + dbName: config.mongoModelsRepo.dbName, + logger, + url: config.mongoModelsRepo.url, + }), + }); + } +}; + +/* istanbul ignore next */ +const getModelsRepo = (): ModelsRepo => { + switch (config.repoFactory.modelsRepoName) { + case 'mongo': + return mongoModelsRepo({ + db: connectToDb({ + dbName: config.mongoModelsRepo.dbName, + logger, + url: config.mongoModelsRepo.url, + }), + }); + default: case 'memory': + return memoryModelsRepo({ + state: { + agentProfiles: [] as Profile[], + }, + }); + } +}; + +/* istanbul ignore next */ +const getStorageRepo = (): StorageRepo => { + switch (config.repoFactory.storageRepoName) { + case 's3': + return s3StorageRepo({ + bucketName: config.s3StorageRepo.bucketName, + client: new S3(config.s3StorageRepo.awsConfig) as any, + subFolder: config.storageSubFolders.agents, + }); + case 'google': + return googleStorageRepo({ + bucketName: config.googleStorageRepo.bucketName, + storage: Storage({ + keyFilename: config.googleStorageRepo.keyFileName, + projectId: config.googleStorageRepo.projectId, + }), + subFolder: config.googleStorageRepo.subFolder.replace(/^\//, ''), + }); + case 'azure': + const credential = new SharedKeyCredential( + config.azureStorageRepo.account, + config.azureStorageRepo.accountKey, + ); + const pipeline = StorageURL.newPipeline(credential); + const serviceURL = new ServiceURL( + `https://${config.azureStorageRepo.account}.blob.core.windows.net`, pipeline, + ); + const containerUrl = ContainerURL.fromServiceURL( + serviceURL, + config.azureStorageRepo.containerName, + ); + + return azureStorageRepo({ + containerUrl, + subFolder: config.azureStorageRepo.subFolder.replace(/^\//, ''), + }); + default: + case 'local': + return localStorageRepo(config.localStorageRepo); + } +}; + +export default (): Repo => { + const authRepo = getAuthRepo(); + const modelsRepo = getModelsRepo(); + const storageRepo = getStorageRepo(); + + return { + ...authRepo, + ...modelsRepo, + ...storageRepo, + + clearRepo: async () => { + await modelsRepo.clearRepo(); + await storageRepo.clearRepo(); + }, + migrate: async () => { + await modelsRepo.migrate(); + await storageRepo.migrate(); + }, + rollback: async () => { + await modelsRepo.rollback(); + await storageRepo.rollback(); + }, + }; +}; diff --git a/src/apps/agents/repoFactory/options/DeleteProfileContentOptions.ts b/src/apps/agents/repoFactory/options/DeleteProfileContentOptions.ts new file mode 100644 index 000000000..06a37c7f3 --- /dev/null +++ b/src/apps/agents/repoFactory/options/DeleteProfileContentOptions.ts @@ -0,0 +1,6 @@ +interface Options { + readonly key: string; + readonly lrs_id: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/DeleteProfileOptions.ts b/src/apps/agents/repoFactory/options/DeleteProfileOptions.ts new file mode 100644 index 000000000..6cc32d91f --- /dev/null +++ b/src/apps/agents/repoFactory/options/DeleteProfileOptions.ts @@ -0,0 +1,11 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly ifMatch?: string; + readonly profileId: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/GetClientOptions.ts b/src/apps/agents/repoFactory/options/GetClientOptions.ts new file mode 100644 index 000000000..3792d2cfb --- /dev/null +++ b/src/apps/agents/repoFactory/options/GetClientOptions.ts @@ -0,0 +1,5 @@ +interface Options { + readonly authToken: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/GetProfileContentOptions.ts b/src/apps/agents/repoFactory/options/GetProfileContentOptions.ts new file mode 100644 index 000000000..06a37c7f3 --- /dev/null +++ b/src/apps/agents/repoFactory/options/GetProfileContentOptions.ts @@ -0,0 +1,6 @@ +interface Options { + readonly key: string; + readonly lrs_id: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/GetProfileOptions.ts b/src/apps/agents/repoFactory/options/GetProfileOptions.ts new file mode 100644 index 000000000..722152641 --- /dev/null +++ b/src/apps/agents/repoFactory/options/GetProfileOptions.ts @@ -0,0 +1,10 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly profileId: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/GetProfilesOptions.ts b/src/apps/agents/repoFactory/options/GetProfilesOptions.ts new file mode 100644 index 000000000..1aba69b9e --- /dev/null +++ b/src/apps/agents/repoFactory/options/GetProfilesOptions.ts @@ -0,0 +1,10 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly since?: Date; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/HasProfileOptions.ts b/src/apps/agents/repoFactory/options/HasProfileOptions.ts new file mode 100644 index 000000000..722152641 --- /dev/null +++ b/src/apps/agents/repoFactory/options/HasProfileOptions.ts @@ -0,0 +1,10 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly profileId: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/OverwriteProfileOptions.ts b/src/apps/agents/repoFactory/options/OverwriteProfileOptions.ts new file mode 100644 index 000000000..1249af134 --- /dev/null +++ b/src/apps/agents/repoFactory/options/OverwriteProfileOptions.ts @@ -0,0 +1,16 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly content: any; + readonly contentType: string; + readonly etag: string; + readonly extension: string; + readonly ifMatch?: string; + readonly ifNoneMatch?: string; + readonly profileId: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/PatchProfileOptions.ts b/src/apps/agents/repoFactory/options/PatchProfileOptions.ts new file mode 100644 index 000000000..1249af134 --- /dev/null +++ b/src/apps/agents/repoFactory/options/PatchProfileOptions.ts @@ -0,0 +1,16 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly content: any; + readonly contentType: string; + readonly etag: string; + readonly extension: string; + readonly ifMatch?: string; + readonly ifNoneMatch?: string; + readonly profileId: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/options/StoreProfileContentOptions.ts b/src/apps/agents/repoFactory/options/StoreProfileContentOptions.ts new file mode 100644 index 000000000..af760f1a8 --- /dev/null +++ b/src/apps/agents/repoFactory/options/StoreProfileContentOptions.ts @@ -0,0 +1,7 @@ +interface Options { + readonly key: string; + readonly content: NodeJS.ReadableStream; + readonly lrs_id: string; +} + +export default Options; diff --git a/src/apps/agents/repoFactory/results/DeleteProfileResult.ts b/src/apps/agents/repoFactory/results/DeleteProfileResult.ts new file mode 100644 index 000000000..a931ae114 --- /dev/null +++ b/src/apps/agents/repoFactory/results/DeleteProfileResult.ts @@ -0,0 +1,7 @@ +interface Result { + readonly id: string; + readonly extension: string; + readonly contentType: string; +} + +export default Result; diff --git a/src/apps/agents/repoFactory/results/GetClientResult.ts b/src/apps/agents/repoFactory/results/GetClientResult.ts new file mode 100644 index 000000000..b2534422f --- /dev/null +++ b/src/apps/agents/repoFactory/results/GetClientResult.ts @@ -0,0 +1,7 @@ +import ClientModel from '../../models/ClientModel'; + +interface Result { + readonly client: ClientModel; +} + +export default Result; diff --git a/src/apps/agents/repoFactory/results/GetProfileContentResult.ts b/src/apps/agents/repoFactory/results/GetProfileContentResult.ts new file mode 100644 index 000000000..e711daa32 --- /dev/null +++ b/src/apps/agents/repoFactory/results/GetProfileContentResult.ts @@ -0,0 +1,5 @@ +interface Result { + readonly content: NodeJS.ReadableStream; +} + +export default Result; diff --git a/src/apps/agents/repoFactory/results/GetProfileResult.ts b/src/apps/agents/repoFactory/results/GetProfileResult.ts new file mode 100644 index 000000000..768907927 --- /dev/null +++ b/src/apps/agents/repoFactory/results/GetProfileResult.ts @@ -0,0 +1,10 @@ +interface Result { + readonly id: string; + readonly content: any; + readonly contentType: string; + readonly updatedAt: Date; + readonly etag: string; + readonly extension: string; +} + +export default Result; diff --git a/src/apps/agents/repoFactory/results/GetProfilesResult.ts b/src/apps/agents/repoFactory/results/GetProfilesResult.ts new file mode 100644 index 000000000..879db08f9 --- /dev/null +++ b/src/apps/agents/repoFactory/results/GetProfilesResult.ts @@ -0,0 +1,5 @@ +interface Result { + readonly profileIds: string[]; +} + +export default Result; diff --git a/src/apps/agents/repoFactory/results/HasProfileResult.ts b/src/apps/agents/repoFactory/results/HasProfileResult.ts new file mode 100644 index 000000000..9b53768b1 --- /dev/null +++ b/src/apps/agents/repoFactory/results/HasProfileResult.ts @@ -0,0 +1,5 @@ +interface Result { + readonly hasProfile: boolean; +} + +export default Result; diff --git a/src/apps/agents/repoFactory/results/OverwriteProfileResult.ts b/src/apps/agents/repoFactory/results/OverwriteProfileResult.ts new file mode 100644 index 000000000..6b32295df --- /dev/null +++ b/src/apps/agents/repoFactory/results/OverwriteProfileResult.ts @@ -0,0 +1,6 @@ +interface Result { + readonly id: string; + readonly extension: string; +} + +export default Result; diff --git a/src/apps/agents/s3StorageRepo/Config.ts b/src/apps/agents/s3StorageRepo/Config.ts new file mode 100644 index 000000000..dd341afa6 --- /dev/null +++ b/src/apps/agents/s3StorageRepo/Config.ts @@ -0,0 +1,3 @@ +import CommonConfig from 'jscommons/dist/s3Repo/Config'; + +export default CommonConfig; diff --git a/src/apps/agents/s3StorageRepo/deleteProfileContent.ts b/src/apps/agents/s3StorageRepo/deleteProfileContent.ts new file mode 100644 index 000000000..4ef8ffcaf --- /dev/null +++ b/src/apps/agents/s3StorageRepo/deleteProfileContent.ts @@ -0,0 +1,14 @@ +import DeleteProfileContentOptions from '../repoFactory/options/DeleteProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: DeleteProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + await config.client.deleteObject({ + Bucket: config.bucketName, + Key: filePath, + }).promise(); + }; +}; diff --git a/src/apps/agents/s3StorageRepo/getProfileContent.ts b/src/apps/agents/s3StorageRepo/getProfileContent.ts new file mode 100644 index 000000000..44865d1ad --- /dev/null +++ b/src/apps/agents/s3StorageRepo/getProfileContent.ts @@ -0,0 +1,20 @@ +import GetProfileContentOptions from '../repoFactory/options/GetProfileContentOptions'; +import GetProfileContentResult from '../repoFactory/results/GetProfileContentResult'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: GetProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + await config.client.getObject({ + Bucket: config.bucketName, + Key: filePath, + }).promise(); + const content = config.client.getObject({ + Bucket: config.bucketName, + Key: filePath, + }).createReadStream(); + return { content }; + }; +}; diff --git a/src/apps/agents/s3StorageRepo/index.ts b/src/apps/agents/s3StorageRepo/index.ts new file mode 100644 index 000000000..f0fde4bf5 --- /dev/null +++ b/src/apps/agents/s3StorageRepo/index.ts @@ -0,0 +1,15 @@ +import commonS3Repo from 'jscommons/dist/s3Repo'; +import StorageRepo from '../repoFactory/StorageRepo'; +import Config from './Config'; +import deleteProfileContent from './deleteProfileContent'; +import getProfileContent from './getProfileContent'; +import storeProfileContent from './storeProfileContent'; + +export default (config: Config): StorageRepo => { + return { + ...commonS3Repo(config), + deleteProfileContent: deleteProfileContent(config), + getProfileContent: getProfileContent(config), + storeProfileContent: storeProfileContent(config), + }; +}; diff --git a/src/apps/agents/s3StorageRepo/readme.md b/src/apps/agents/s3StorageRepo/readme.md new file mode 100644 index 000000000..58e694322 --- /dev/null +++ b/src/apps/agents/s3StorageRepo/readme.md @@ -0,0 +1,7 @@ +# Notes +- All top level files and folders should be functions exposed by this layer, with the following exceptions: + - "Config.ts" + - "index.ts" + - "readme.md" + - "tests/" + - "utils/" diff --git a/src/apps/agents/s3StorageRepo/storeProfileContent.ts b/src/apps/agents/s3StorageRepo/storeProfileContent.ts new file mode 100644 index 000000000..f46e82751 --- /dev/null +++ b/src/apps/agents/s3StorageRepo/storeProfileContent.ts @@ -0,0 +1,15 @@ +import StoreProfileContentOptions from '../repoFactory/options/StoreProfileContentOptions'; +import getStorageDir from '../utils/getStorageDir'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: StoreProfileContentOptions): Promise => { + const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id }); + const filePath = `${profileDir}/${opts.key}`; + await config.client.upload({ + Body: opts.content, + Bucket: config.bucketName, + Key: filePath, + }).promise(); + }; +}; diff --git a/src/apps/agents/service/Config.ts b/src/apps/agents/service/Config.ts new file mode 100644 index 000000000..5583dc214 --- /dev/null +++ b/src/apps/agents/service/Config.ts @@ -0,0 +1,7 @@ +import Repo from '../repoFactory/Repo'; + +interface Config { + readonly repo: Repo; +} + +export default Config; diff --git a/src/apps/agents/service/deleteProfile.ts b/src/apps/agents/service/deleteProfile.ts new file mode 100644 index 000000000..02e1d5d47 --- /dev/null +++ b/src/apps/agents/service/deleteProfile.ts @@ -0,0 +1,38 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import DeleteProfileOptions from '../serviceFactory/options/DeleteProfileOptions'; +import { jsonContentType } from '../utils/constants'; +import Config from './Config'; +import checkProfileWriteScopes from './utils/checkProfileWriteScopes'; +import validateAgent from './utils/validateAgent'; + +export default (config: Config) => { + return async (opts: DeleteProfileOptions): Promise => { + const client = opts.client; + checkProfileWriteScopes(client.scopes); + validateAgent(opts.agent); + + try { + const deleteResult = await config.repo.deleteProfile({ + agent: opts.agent, + client, + ifMatch: opts.ifMatch, + profileId: opts.profileId, + }); + + if (deleteResult.contentType === jsonContentType) { + return; + } + + await config.repo.deleteProfileContent({ + key: `${deleteResult.id}.${deleteResult.extension}`, + lrs_id: opts.client.lrs_id, + }); + } catch (err) { + if (err instanceof NoModel) { + return; + } + /* istanbul ignore next */ + throw err; + } + }; +}; diff --git a/src/apps/agents/service/getClient.ts b/src/apps/agents/service/getClient.ts new file mode 100644 index 000000000..b99602eb7 --- /dev/null +++ b/src/apps/agents/service/getClient.ts @@ -0,0 +1,20 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import Unauthorised from 'jscommons/dist/errors/Unauthorised'; +import { isObject } from 'lodash'; +import GetClientOptions from '../serviceFactory/options/GetClientOptions'; +import GetClientResult from '../serviceFactory/results/GetClientResult'; +import Config from './Config'; + +export default (config: Config) => { + return async (opts: GetClientOptions): Promise => { + try { + return await config.repo.getClient(opts); + } catch (err) { + if (isObject(err) && err.constructor === NoModel) { + throw new Unauthorised(); + } + /* istanbul ignore next */ + throw err; + } + }; +}; diff --git a/src/apps/agents/service/getFullAgent.ts b/src/apps/agents/service/getFullAgent.ts new file mode 100644 index 000000000..3067edc7d --- /dev/null +++ b/src/apps/agents/service/getFullAgent.ts @@ -0,0 +1,25 @@ +import Agent from '../models/Agent'; +import GetFullAgentOptions from '../serviceFactory/options/GetFullAgentOptions'; +import GetFullAgentResult from '../serviceFactory/results/GetFullAgentResult'; +import Config from './Config'; +import checkProfileReadScopes from './utils/checkProfileReadScopes'; +import validateAgent from './utils/validateAgent'; + +const getCurrentFullAgent = (agent: Agent) => { + return { + account: agent.account === undefined ? [] : [agent.account], + mbox: agent.mbox === undefined ? [] : [agent.mbox], + mbox_sha1sum: agent.mbox_sha1sum === undefined ? [] : [agent.mbox_sha1sum], + name: [], + objectType: 'Person', + openid: agent.openid === undefined ? [] : [agent.openid], + }; +}; + +export default (_config: Config) => { + return async (opts: GetFullAgentOptions): Promise => { + checkProfileReadScopes(opts.client.scopes); + validateAgent(opts.agent); + return getCurrentFullAgent(opts.agent); + }; +}; diff --git a/src/apps/agents/service/getProfile.ts b/src/apps/agents/service/getProfile.ts new file mode 100644 index 000000000..3a9de8244 --- /dev/null +++ b/src/apps/agents/service/getProfile.ts @@ -0,0 +1,38 @@ +import stringToStream from 'string-to-stream'; +import GetProfileOptions from '../serviceFactory/options/GetProfileOptions'; +import GetProfileResult from '../serviceFactory/results/GetProfileResult'; +import Config from './Config'; +import checkProfileReadScopes from './utils/checkProfileReadScopes'; +import validateAgent from './utils/validateAgent'; + +export default (config: Config) => { + return async (opts: GetProfileOptions): Promise => { + checkProfileReadScopes(opts.client.scopes); + validateAgent(opts.agent); + const profile = await config.repo.getProfile({ + agent: opts.agent, + client: opts.client, + profileId: opts.profileId, + }); + + if (profile.content !== undefined) { + return { + content: stringToStream(JSON.stringify(profile.content)), + contentType: profile.contentType, + etag: profile.etag, + updatedAt: profile.updatedAt, + }; + } + + const profileContentResult = await config.repo.getProfileContent({ + key: `${profile.id}.${profile.extension}`, + lrs_id: opts.client.lrs_id, + }); + return { + content: profileContentResult.content, + contentType: profile.contentType, + etag: profile.etag, + updatedAt: profile.updatedAt, + }; + }; +}; diff --git a/src/apps/agents/service/getProfiles.ts b/src/apps/agents/service/getProfiles.ts new file mode 100644 index 000000000..2ccec14fa --- /dev/null +++ b/src/apps/agents/service/getProfiles.ts @@ -0,0 +1,30 @@ +import GetProfilesOptions from '../serviceFactory/options/GetProfilesOptions'; +import GetProfilesResult from '../serviceFactory/results/GetProfilesResult'; +import Config from './Config'; +import checkProfileReadScopes from './utils/checkProfileReadScopes'; +import validateAgent from './utils/validateAgent'; +import validateSince from './utils/validateSince'; + +const getSince = (since?: string): Date|undefined => { + if (since !== undefined) { + validateSince(since); + return new Date(since); + } else { + return undefined; + } +}; + +export default (config: Config) => { + return async (opts: GetProfilesOptions): Promise => { + checkProfileReadScopes(opts.client.scopes); + validateAgent(opts.agent); + const since = getSince(opts.since); + const profileIds = (await config.repo.getProfiles({ + agent: opts.agent, + client: opts.client, + since, + })).profileIds; + + return { profileIds }; + }; +}; diff --git a/src/apps/agents/service/index.ts b/src/apps/agents/service/index.ts new file mode 100644 index 000000000..3cdf25ad5 --- /dev/null +++ b/src/apps/agents/service/index.ts @@ -0,0 +1,23 @@ +import commonService from 'jscommons/dist/service'; +import Service from '../serviceFactory/Service'; +import Config from './Config'; +import deleteProfile from './deleteProfile'; +import getClient from './getClient'; +import getFullAgent from './getFullAgent'; +import getProfile from './getProfile'; +import getProfiles from './getProfiles'; +import overwriteProfile from './overwriteProfile'; +import patchProfile from './patchProfile'; + +export default (config: Config): Service => { + return { + ...commonService(config), + deleteProfile: deleteProfile(config), + getClient: getClient(config), + getFullAgent: getFullAgent(config), + getProfile: getProfile(config), + getProfiles: getProfiles(config), + overwriteProfile: overwriteProfile(config), + patchProfile: patchProfile(config), + }; +}; diff --git a/src/apps/agents/service/overwriteProfile.ts b/src/apps/agents/service/overwriteProfile.ts new file mode 100644 index 000000000..485dea72c --- /dev/null +++ b/src/apps/agents/service/overwriteProfile.ts @@ -0,0 +1,62 @@ +import streamToString from 'stream-to-string'; +import Conflict from '../errors/Conflict'; +import MissingEtags from '../errors/MissingEtags'; +import OverwriteProfileOptions from '../serviceFactory/options/OverwriteProfileOptions'; +import { jsonContentType } from '../utils/constants'; +import getFileExtension from '../utils/getFileExtension'; +import parseJson from '../utils/parseJson'; +import Config from './Config'; +import checkProfileWriteScopes from './utils/checkProfileWriteScopes'; +import createEtag from './utils/createEtag'; +import validateAgent from './utils/validateAgent'; + +export default (config: Config) => { + return async (opts: OverwriteProfileOptions) => { + checkProfileWriteScopes(opts.client.scopes); + validateAgent(opts.agent); + const etag = createEtag(); + + if (opts.ifMatch === undefined && opts.ifNoneMatch === undefined) { + const { hasProfile } = await config.repo.hasProfile({ + agent: opts.agent, + client: opts.client, + profileId: opts.profileId, + }); + if (hasProfile) { + throw new Conflict(); + } else { + throw new MissingEtags(); + } + } + + // Update or create Profile. + const jsonContent = ( + opts.contentType === jsonContentType + ? parseJson(await streamToString(opts.content), ['content']) + : undefined + ); + + const extension = getFileExtension(opts.contentType); + const overwriteProfileResult = await config.repo.overwriteProfile({ + agent: opts.agent, + client: opts.client, + content: jsonContent, + contentType: opts.contentType, + etag, + extension, + ifMatch: opts.ifMatch, + ifNoneMatch: opts.ifNoneMatch, + profileId: opts.profileId, + }); + + if (opts.contentType !== jsonContentType) { + await config.repo.storeProfileContent({ + content: opts.content, + key: `${overwriteProfileResult.id}.${extension}`, + lrs_id: opts.client.lrs_id, + }); + } + + return; + }; +}; diff --git a/src/apps/agents/service/patchProfile.ts b/src/apps/agents/service/patchProfile.ts new file mode 100644 index 000000000..036a5bfa5 --- /dev/null +++ b/src/apps/agents/service/patchProfile.ts @@ -0,0 +1,44 @@ +import { isPlainObject } from 'lodash'; +import streamToString from 'stream-to-string'; +import NonJsonObject from '../errors/NonJsonObject'; +import PatchProfileOptions from '../serviceFactory/options/PatchProfileOptions'; +import { jsonContentType } from '../utils/constants'; +import getFileExtension from '../utils/getFileExtension'; +import parseJson from '../utils/parseJson'; +import Config from './Config'; +import checkProfileWriteScopes from './utils/checkProfileWriteScopes'; +import createEtag from './utils/createEtag'; +import validateAgent from './utils/validateAgent'; + +export default (config: Config) => { + return async (opts: PatchProfileOptions): Promise => { + const client = opts.client; + checkProfileWriteScopes(client.scopes); + validateAgent(opts.agent); + + if (opts.contentType !== jsonContentType) { + throw new NonJsonObject(); + } + + const content = parseJson(await streamToString(opts.content), ['content']); + if (!isPlainObject(content)) { + throw new NonJsonObject(); + } + + const extension = getFileExtension(opts.contentType); + + const etag = createEtag(); + await config.repo.patchProfile({ + agent: opts.agent, + client, + content, + contentType: opts.contentType, + etag, + extension, + ifMatch: opts.ifMatch, + ifNoneMatch: opts.ifNoneMatch, + profileId: opts.profileId, + }); + return; + }; +}; diff --git a/src/apps/agents/service/tests/deleteProfile/deleteExistingProfile.test.ts b/src/apps/agents/service/tests/deleteProfile/deleteExistingProfile.test.ts new file mode 100644 index 000000000..38690834a --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/deleteExistingProfile.test.ts @@ -0,0 +1,51 @@ +import assertDeleted from '../../../utils/assertDeleted'; +import createJsonProfile from '../../../utils/createJsonProfile'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + TEST_ACCOUNT_AGENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('deleteProfile with existing state', () => { + setup(); + + it('should delete when deleting text', async () => { + await createTextProfile(); + await deleteProfile(); + await assertDeleted(); + }); + + it('should delete when deleting json', async () => { + await createJsonProfile(); + await deleteProfile(); + await assertDeleted(); + }); + + it('should delete when deleting with an mbox', async () => { + await createTextProfile({ agent: TEST_MBOX_AGENT }); + await deleteProfile({ agent: TEST_MBOX_AGENT }); + await assertDeleted({ agent: TEST_MBOX_AGENT }); + }); + + it('should delete when deleting with an mbox_sha1sum', async () => { + await createTextProfile({ agent: TEST_MBOXSHA1_AGENT }); + await deleteProfile({ agent: TEST_MBOXSHA1_AGENT }); + await assertDeleted({ agent: TEST_MBOXSHA1_AGENT }); + }); + + it('should delete when deleting with an openid', async () => { + await createTextProfile({ agent: TEST_OPENID_AGENT }); + await deleteProfile({ agent: TEST_OPENID_AGENT }); + await assertDeleted({ agent: TEST_OPENID_AGENT }); + }); + + it('should delete when deleting with an account', async () => { + await createTextProfile({ agent: TEST_ACCOUNT_AGENT }); + await deleteProfile({ agent: TEST_ACCOUNT_AGENT }); + await assertDeleted({ agent: TEST_ACCOUNT_AGENT }); + }); +}); diff --git a/src/apps/agents/service/tests/deleteProfile/deleteNonExistingProfile.test.ts b/src/apps/agents/service/tests/deleteProfile/deleteNonExistingProfile.test.ts new file mode 100644 index 000000000..e5f4250a5 --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/deleteNonExistingProfile.test.ts @@ -0,0 +1,18 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { Warnings } from 'rulr'; +import { TEST_INVALID_AGENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('deleteProfile with non-existing profile', () => { + setup(); + + it('should not error when deleting', async () => { + await deleteProfile(); + }); + + it('should throw warnings when using an invalid agent', async () => { + const promise = deleteProfile({ agent: TEST_INVALID_AGENT }); + await assertError(Warnings, promise); + }); +}); diff --git a/src/apps/agents/service/tests/deleteProfile/deleteOutsideOrg.test.ts b/src/apps/agents/service/tests/deleteProfile/deleteOutsideOrg.test.ts new file mode 100644 index 000000000..671201e6d --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/deleteOutsideOrg.test.ts @@ -0,0 +1,23 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { + TEST_CLIENT_OUTSIDE_ORG, + TEST_CONTENT, + TEST_OBJECT_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('deleteProfile outside the organisation', () => { + setup(); + + it('should error when deleting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_ORG, TEST_CONTENT); + }); + + it('should error when deleting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_ORG, TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/service/tests/deleteProfile/deleteOutsideStore.test.ts b/src/apps/agents/service/tests/deleteProfile/deleteOutsideStore.test.ts new file mode 100644 index 000000000..1c47b4008 --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/deleteOutsideStore.test.ts @@ -0,0 +1,23 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { + TEST_CLIENT_OUTSIDE_STORE, + TEST_CONTENT, + TEST_OBJECT_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('deleteProfile outside the store', () => { + setup(); + + it('should error when deleting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_STORE, TEST_CONTENT); + }); + + it('should error when deleting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(TEST_CLIENT_OUTSIDE_STORE, TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/service/tests/deleteProfile/deleteWithEtags.test.ts b/src/apps/agents/service/tests/deleteProfile/deleteWithEtags.test.ts new file mode 100644 index 000000000..f6663a98c --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/deleteWithEtags.test.ts @@ -0,0 +1,27 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import IfMatch from '../../../errors/IfMatch'; +import createTextProfile from '../../../utils/createTextProfile'; +import getTestProfile from '../../../utils/getTestProfile'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('deleteProfile with etags', () => { + setup(); + + it('should allow deletion when using a correct etag', async () => { + await createTextProfile(); + const getProfileResult = await getTestProfile(); + await deleteProfile({ ifMatch: getProfileResult.etag }); + }); + + it('should throw precondition error when using an incorrect ifMatch', async () => { + await createTextProfile(); + const promise = deleteProfile({ ifMatch: 'incorrect_etag' }); + await assertError(IfMatch, promise); + }); + + it('should allow deletion when not using an IfMatch', async () => { + await createTextProfile(); + await deleteProfile(); + }); +}); diff --git a/src/apps/agents/service/tests/deleteProfile/deleteWithScopes.test.ts b/src/apps/agents/service/tests/deleteProfile/deleteWithScopes.test.ts new file mode 100644 index 000000000..491837725 --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/deleteWithScopes.test.ts @@ -0,0 +1,20 @@ +import Forbidden from 'jscommons/dist/errors/Forbidden'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { TEST_INVALID_SCOPE_CLIENT, TEST_VALID_SCOPE_CLIENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import deleteProfile from './utils/deleteProfile'; + +describe('deleteProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + const promise = deleteProfile({ + client: TEST_INVALID_SCOPE_CLIENT, + }); + await assertError(Forbidden, promise); + }); + + it('should not error when using valid scopes', async () => { + await deleteProfile({ client: TEST_VALID_SCOPE_CLIENT }); + }); +}); diff --git a/src/apps/agents/service/tests/deleteProfile/utils/assertOutsideClient.ts b/src/apps/agents/service/tests/deleteProfile/utils/assertOutsideClient.ts new file mode 100644 index 000000000..feceeee4c --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/utils/assertOutsideClient.ts @@ -0,0 +1,8 @@ +import ClientModel from '../../../../models/ClientModel'; +import assertProfile from '../../../../utils/assertProfile'; +import deleteProfile from './deleteProfile'; + +export default async (client: ClientModel, content: string) => { + await deleteProfile(); + await assertProfile(content, { client }); +}; diff --git a/src/apps/agents/service/tests/deleteProfile/utils/deleteProfile.ts b/src/apps/agents/service/tests/deleteProfile/utils/deleteProfile.ts new file mode 100644 index 000000000..f082d409e --- /dev/null +++ b/src/apps/agents/service/tests/deleteProfile/utils/deleteProfile.ts @@ -0,0 +1,12 @@ +import DeleteProfileOptions from '../../../../serviceFactory/options/DeleteProfileOptions'; +import service from '../../../../utils/testService'; +import { TEST_CLIENT, TEST_MBOX_AGENT, TEST_PROFILE_ID } from '../../../../utils/testValues'; + +export default async (optsOverrides: Partial = {}) => { + await service.deleteProfile({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/service/tests/getClient/getNonExistingClient.test.ts b/src/apps/agents/service/tests/getClient/getNonExistingClient.test.ts new file mode 100644 index 000000000..d2db57003 --- /dev/null +++ b/src/apps/agents/service/tests/getClient/getNonExistingClient.test.ts @@ -0,0 +1,13 @@ +import Unauthorised from 'jscommons/dist/errors/Unauthorised'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { TEST_MISSING_TOKEN } from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('getClient using non-existing model', () => { + const service = setup(); + + it('should error when getting without clients', async () => { + const promise = service.getClient({ authToken: TEST_MISSING_TOKEN }); + await assertError(Unauthorised, promise); + }); +}); diff --git a/src/apps/agents/service/tests/getFullAgent/getNonExistingModel.test.ts b/src/apps/agents/service/tests/getFullAgent/getNonExistingModel.test.ts new file mode 100644 index 000000000..0d6abd0b2 --- /dev/null +++ b/src/apps/agents/service/tests/getFullAgent/getNonExistingModel.test.ts @@ -0,0 +1,61 @@ +import * as assert from 'assert'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { Warnings } from 'rulr'; +import Account from '../../../models/Account'; +import GetFullAgentResult from '../../../serviceFactory/results/GetFullAgentResult'; +import { + TEST_ACCOUNT_AGENT, + TEST_INVALID_AGENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getFullAgent from './utils/getFullAgent'; + +describe('getFullAgent with non-existing model', () => { + setup(); + + const assertFullAgent = async (agent: any, resultOverrides: Partial) => { + const expectedResult: GetFullAgentResult = { + account: [], + mbox: [], + mbox_sha1sum: [], + name: [], + objectType: 'Person', + openid: [], + ...resultOverrides, + }; + const fullAgent = await getFullAgent({ agent }); + assert.deepEqual(fullAgent, expectedResult); + }; + + it('should return the agent when using mbox', async () => { + await assertFullAgent(TEST_MBOX_AGENT, { + mbox: [TEST_MBOX_AGENT.mbox as string], + }); + }); + + it('should return the agent when using mbox_sha1sum', async () => { + await assertFullAgent(TEST_MBOXSHA1_AGENT, { + mbox_sha1sum: [TEST_MBOXSHA1_AGENT.mbox_sha1sum as string], + }); + }); + + it('should return the agent when using openid', async () => { + await assertFullAgent(TEST_OPENID_AGENT, { + openid: [TEST_OPENID_AGENT.openid as string], + }); + }); + + it('should return the agent when using account', async () => { + await assertFullAgent(TEST_ACCOUNT_AGENT, { + account: [TEST_ACCOUNT_AGENT.account as Account], + }); + }); + + it('should throw warnings when using an invalid agent', async () => { + const promise = getFullAgent({ agent: TEST_INVALID_AGENT }); + await assertError(Warnings, promise); + }); +}); diff --git a/src/apps/agents/service/tests/getFullAgent/getWithScopes.test.ts b/src/apps/agents/service/tests/getFullAgent/getWithScopes.test.ts new file mode 100644 index 000000000..30a5e44b1 --- /dev/null +++ b/src/apps/agents/service/tests/getFullAgent/getWithScopes.test.ts @@ -0,0 +1,22 @@ +import Forbidden from 'jscommons/dist/errors/Forbidden'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { TEST_INVALID_SCOPE_CLIENT, TEST_VALID_SCOPE_CLIENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import getFullAgent from './utils/getFullAgent'; + +describe('getFullAgent with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + const promise = getFullAgent({ + client: TEST_INVALID_SCOPE_CLIENT, + }); + await assertError(Forbidden, promise); + }); + + it('should not throw error when using valid scopes', async () => { + await getFullAgent({ + client: TEST_VALID_SCOPE_CLIENT, + }); + }); +}); diff --git a/src/apps/agents/service/tests/getFullAgent/utils/getFullAgent.ts b/src/apps/agents/service/tests/getFullAgent/utils/getFullAgent.ts new file mode 100644 index 000000000..6e2f86e96 --- /dev/null +++ b/src/apps/agents/service/tests/getFullAgent/utils/getFullAgent.ts @@ -0,0 +1,17 @@ +import GetFullAgentOptions from '../../../../serviceFactory/options/GetFullAgentOptions'; +import GetFullAgentResult from '../../../../serviceFactory/results/GetFullAgentResult'; +import service from '../../../../utils/testService'; +import { + TEST_CLIENT, + TEST_MBOX_AGENT, +} from '../../../../utils/testValues'; + +export default async ( + optsOverrides: Partial = {}, +): Promise => { + return service.getFullAgent({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/service/tests/getProfile/getExistingProfile.test.ts b/src/apps/agents/service/tests/getProfile/getExistingProfile.test.ts new file mode 100644 index 000000000..ffcccf284 --- /dev/null +++ b/src/apps/agents/service/tests/getProfile/getExistingProfile.test.ts @@ -0,0 +1,91 @@ +import * as assert from 'assert'; +import streamToString from 'stream-to-string'; +import GetProfileResult from '../../../serviceFactory/results/GetProfileResult'; +import createJsonProfile from '../../../utils/createJsonProfile'; +import createTextProfile from '../../../utils/createTextProfile'; +import getTestProfile from '../../../utils/getTestProfile'; +import { + JSON_CONTENT_TYPE, + TEST_ACCOUNT_AGENT, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('getProfile with existing profile', () => { + setup(); + + const assertGetProfile = async ( + result: GetProfileResult, + content: string, + contentType: string, + ) => { + const actualContent = await streamToString(result.content); + assert.equal(actualContent, content); + assert.equal(result.contentType, contentType); + assert.equal(result.updatedAt.constructor, Date); + assert.equal(result.etag.constructor, String); + }; + + it('should get when getting text', async () => { + await createTextProfile(); + const agentProfileResult = await getTestProfile(); + await assertGetProfile(agentProfileResult, TEST_CONTENT, TEXT_CONTENT_TYPE); + }); + + it('should get when agent properties are in a different order', async () => { + // tslint:disable:object-literal-sort-keys + const creationAgent = { + objectType: 'Agent', + account: { + name: 'steely.eyed', + homePage: 'http://missile.man', + }, + }; + const retrievalAgent = { + objectType: 'Agent', + account: { + homePage: 'http://missile.man', + name: 'steely.eyed', + }, + }; + // tslint:enable:object-literal-sort-keys + await createTextProfile({ agent: creationAgent }); + const agentProfileResult = await getTestProfile({ agent: retrievalAgent }); + await assertGetProfile(agentProfileResult, TEST_CONTENT, TEXT_CONTENT_TYPE); + }); + + it('should get when getting json', async () => { + await createJsonProfile(); + const agentProfileResult = await getTestProfile(); + await assertGetProfile(agentProfileResult, TEST_JSON_CONTENT, JSON_CONTENT_TYPE); + }); + + it('should get when using an mbox', async () => { + await createTextProfile({ agent: TEST_MBOX_AGENT }); + const agentProfileResult = await getTestProfile({ agent: TEST_MBOX_AGENT }); + await assertGetProfile(agentProfileResult, TEST_CONTENT, TEXT_CONTENT_TYPE); + }); + + it('should get when using an mbox_sha1sum', async () => { + await createTextProfile({ agent: TEST_MBOXSHA1_AGENT }); + const agentProfileResult = await getTestProfile({ agent: TEST_MBOXSHA1_AGENT }); + await assertGetProfile(agentProfileResult, TEST_CONTENT, TEXT_CONTENT_TYPE); + }); + + it('should get when using an openid', async () => { + await createTextProfile({ agent: TEST_OPENID_AGENT }); + const agentProfileResult = await getTestProfile({ agent: TEST_OPENID_AGENT }); + await assertGetProfile(agentProfileResult, TEST_CONTENT, TEXT_CONTENT_TYPE); + }); + + it('should get when using an account', async () => { + await createTextProfile({ agent: TEST_ACCOUNT_AGENT }); + const agentProfileResult = await getTestProfile({ agent: TEST_ACCOUNT_AGENT }); + await assertGetProfile(agentProfileResult, TEST_CONTENT, TEXT_CONTENT_TYPE); + }); +}); diff --git a/src/apps/agents/service/tests/getProfile/getNonExistingProfile.test.ts b/src/apps/agents/service/tests/getProfile/getNonExistingProfile.test.ts new file mode 100644 index 000000000..e4ceed94e --- /dev/null +++ b/src/apps/agents/service/tests/getProfile/getNonExistingProfile.test.ts @@ -0,0 +1,22 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { Warnings } from 'rulr'; +import getTestProfile from '../../../utils/getTestProfile'; +import { TEST_INVALID_AGENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('getProfile with non-existing model', () => { + setup(); + + it('should error when getting a non-existing model', async () => { + const promise = getTestProfile(); + await assertError(NoModel, promise); + }); + + it('should throw warnings when using an invalid agent', async () => { + const promise = getTestProfile({ + agent: TEST_INVALID_AGENT, + }); + await assertError(Warnings, promise); + }); +}); diff --git a/src/apps/agents/service/tests/getProfile/getOutsideOrg.test.ts b/src/apps/agents/service/tests/getProfile/getOutsideOrg.test.ts new file mode 100644 index 000000000..2da972061 --- /dev/null +++ b/src/apps/agents/service/tests/getProfile/getOutsideOrg.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_ORG } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('getProfile outside the organisation', () => { + setup(); + + it('should error when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); + + it('should error when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/service/tests/getProfile/getOutsideStore.test.ts b/src/apps/agents/service/tests/getProfile/getOutsideStore.test.ts new file mode 100644 index 000000000..0daf113d2 --- /dev/null +++ b/src/apps/agents/service/tests/getProfile/getOutsideStore.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_STORE } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('getProfile outside the store', () => { + setup(); + + it('should error when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); + + it('should error when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/service/tests/getProfile/getWithScopes.test.ts b/src/apps/agents/service/tests/getProfile/getWithScopes.test.ts new file mode 100644 index 000000000..098baafb8 --- /dev/null +++ b/src/apps/agents/service/tests/getProfile/getWithScopes.test.ts @@ -0,0 +1,24 @@ +import Forbidden from 'jscommons/dist/errors/Forbidden'; +import NoModel from 'jscommons/dist/errors/NoModel'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import getTestProfile from '../../../utils/getTestProfile'; +import { TEST_INVALID_SCOPE_CLIENT, TEST_VALID_SCOPE_CLIENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('getProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + const promise = getTestProfile({ + client: TEST_INVALID_SCOPE_CLIENT, + }); + await assertError(Forbidden, promise); + }); + + it('should throw no model error when using valid scopes', async () => { + const promise = getTestProfile({ + client: TEST_VALID_SCOPE_CLIENT, + }); + await assertError(NoModel, promise); + }); +}); diff --git a/src/apps/agents/service/tests/getProfile/utils/assertOutsideClient.ts b/src/apps/agents/service/tests/getProfile/utils/assertOutsideClient.ts new file mode 100644 index 000000000..ee9624eb5 --- /dev/null +++ b/src/apps/agents/service/tests/getProfile/utils/assertOutsideClient.ts @@ -0,0 +1,8 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import getTestProfile from '../../../../utils/getTestProfile'; + +export default async () => { + const promise = getTestProfile(); + await assertError(NoModel, promise); +}; diff --git a/src/apps/agents/service/tests/getProfiles/getExistingProfiles.test.ts b/src/apps/agents/service/tests/getProfiles/getExistingProfiles.test.ts new file mode 100644 index 000000000..e09fafa5a --- /dev/null +++ b/src/apps/agents/service/tests/getProfiles/getExistingProfiles.test.ts @@ -0,0 +1,45 @@ +import * as assert from 'assert'; +import createTextProfile from '../../../utils/createTextProfile'; +import getTestProfiles from '../../../utils/getTestProfiles'; +import { + TEST_ACCOUNT_AGENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('getProfiles with existing model', () => { + setup(); + + it('should return profile ids when getting a existing model', async () => { + await createTextProfile(); + const profilesResult = await getTestProfiles(); + assert.deepEqual(profilesResult.profileIds, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an mbox', async () => { + await createTextProfile({ agent: TEST_MBOX_AGENT }); + const profilesResult = await getTestProfiles({ agent: TEST_MBOX_AGENT }); + assert.deepEqual(profilesResult.profileIds, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an mbox_sha1sum', async () => { + await createTextProfile({ agent: TEST_MBOXSHA1_AGENT }); + const profilesResult = await getTestProfiles({ agent: TEST_MBOXSHA1_AGENT }); + assert.deepEqual(profilesResult.profileIds, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an openid', async () => { + await createTextProfile({ agent: TEST_OPENID_AGENT }); + const profilesResult = await getTestProfiles({ agent: TEST_OPENID_AGENT }); + assert.deepEqual(profilesResult.profileIds, [TEST_PROFILE_ID]); + }); + + it('should return profile ids when using an account', async () => { + await createTextProfile({ agent: TEST_ACCOUNT_AGENT }); + const profilesResult = await getTestProfiles({ agent: TEST_ACCOUNT_AGENT }); + assert.deepEqual(profilesResult.profileIds, [TEST_PROFILE_ID]); + }); +}); diff --git a/src/apps/agents/service/tests/getProfiles/getNonExistingProfiles.test.ts b/src/apps/agents/service/tests/getProfiles/getNonExistingProfiles.test.ts new file mode 100644 index 000000000..f36affb1b --- /dev/null +++ b/src/apps/agents/service/tests/getProfiles/getNonExistingProfiles.test.ts @@ -0,0 +1,32 @@ +import * as assert from 'assert'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { Warnings } from 'rulr'; +import getTestProfiles from '../../../utils/getTestProfiles'; +import { + TEST_INVALID_AGENT, + TEST_INVALID_TIMESTAMP, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('getProfiles with non-existing agent', () => { + setup(); + + it('should return no profile ids when getting a non-existing agent', async () => { + const profilesResult = await getTestProfiles(); + assert.deepEqual(profilesResult.profileIds, []); + }); + + it('should throw warnings when using an invalid agent', async () => { + const promise = getTestProfiles({ + agent: TEST_INVALID_AGENT, + }); + await assertError(Warnings, promise); + }); + + it('should throw warnings when using an invalid since', async () => { + const promise = getTestProfiles({ + since: TEST_INVALID_TIMESTAMP, + }); + await assertError(Warnings, promise); + }); +}); diff --git a/src/apps/agents/service/tests/getProfiles/getOutsideOrg.test.ts b/src/apps/agents/service/tests/getProfiles/getOutsideOrg.test.ts new file mode 100644 index 000000000..797d1af20 --- /dev/null +++ b/src/apps/agents/service/tests/getProfiles/getOutsideOrg.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_ORG } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('getProfiles outside the organisation', () => { + setup(); + + it('should return no profile ids when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); + + it('should return no profile ids when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_ORG); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/service/tests/getProfiles/getOutsideStore.test.ts b/src/apps/agents/service/tests/getProfiles/getOutsideStore.test.ts new file mode 100644 index 000000000..0b3b8c5d0 --- /dev/null +++ b/src/apps/agents/service/tests/getProfiles/getOutsideStore.test.ts @@ -0,0 +1,19 @@ +import overwriteProfileOutsideClient from '../../../utils/overwriteProfileOutsideClient'; +import patchProfileOutsideClient from '../../../utils/patchProfileOutsideClient'; +import { TEST_CLIENT_OUTSIDE_STORE } from '../../../utils/testValues'; +import setup from '../utils/setup'; +import assertOutsideClient from './utils/assertOutsideClient'; + +describe('getProfiles outside the store', () => { + setup(); + + it('should return no profile ids when getting a overwritten model', async () => { + await overwriteProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); + + it('should return no profile ids when getting a patched model', async () => { + await patchProfileOutsideClient(TEST_CLIENT_OUTSIDE_STORE); + await assertOutsideClient(); + }); +}); diff --git a/src/apps/agents/service/tests/getProfiles/getWithScopes.test.ts b/src/apps/agents/service/tests/getProfiles/getWithScopes.test.ts new file mode 100644 index 000000000..5b098596e --- /dev/null +++ b/src/apps/agents/service/tests/getProfiles/getWithScopes.test.ts @@ -0,0 +1,24 @@ +import * as assert from 'assert'; +import Forbidden from 'jscommons/dist/errors/Forbidden'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import getTestProfiles from '../../../utils/getTestProfiles'; +import { TEST_INVALID_SCOPE_CLIENT, TEST_VALID_SCOPE_CLIENT } from '../../../utils/testValues'; +import setup from '../utils/setup'; + +describe('getProfiles with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + const promise = getTestProfiles({ + client: TEST_INVALID_SCOPE_CLIENT, + }); + await assertError(Forbidden, promise); + }); + + it('should return no models when using valid scopes', async () => { + const getProfilesResult = await getTestProfiles({ + client: TEST_VALID_SCOPE_CLIENT, + }); + assert.deepEqual(getProfilesResult.profileIds, []); + }); +}); diff --git a/src/apps/agents/service/tests/getProfiles/getWithSince.test.ts b/src/apps/agents/service/tests/getProfiles/getWithSince.test.ts new file mode 100644 index 000000000..7d5fe1883 --- /dev/null +++ b/src/apps/agents/service/tests/getProfiles/getWithSince.test.ts @@ -0,0 +1,34 @@ +import * as assert from 'assert'; +import { delay } from 'bluebird'; +import createTextProfile from '../../../utils/createTextProfile'; +import getTestProfiles from '../../../utils/getTestProfiles'; +import { TEST_PROFILE_ID } from '../../../utils/testValues'; +import setup from '../utils/setup'; + +const TEST_DELAY_MS = 2; + +describe('getProfiles with since', () => { + setup(); + + const getProfiles = async (timestamp: Date) => { + return getTestProfiles({ + since: timestamp.toISOString(), + }); + }; + + it('should return no profile ids when updated before since', async () => { + await createTextProfile(); + await Promise.resolve(delay(TEST_DELAY_MS)); + const timestamp = new Date(); + const getProfilesResult = await getProfiles(timestamp); + assert.deepEqual(getProfilesResult.profileIds, []); + }); + + it('should return the profile id when updated after since', async () => { + const timestamp = new Date(); + await Promise.resolve(delay(TEST_DELAY_MS)); + await createTextProfile(); + const getProfilesResult = await getProfiles(timestamp); + assert.deepEqual(getProfilesResult.profileIds, [TEST_PROFILE_ID]); + }); +}); diff --git a/src/apps/agents/service/tests/getProfiles/utils/assertOutsideClient.ts b/src/apps/agents/service/tests/getProfiles/utils/assertOutsideClient.ts new file mode 100644 index 000000000..2383b0357 --- /dev/null +++ b/src/apps/agents/service/tests/getProfiles/utils/assertOutsideClient.ts @@ -0,0 +1,7 @@ +import * as assert from 'assert'; +import getTestProfiles from '../../../../utils/getTestProfiles'; + +export default async () => { + const profilesResult = await getTestProfiles(); + assert.deepEqual(profilesResult.profileIds, []); +}; diff --git a/src/apps/agents/service/tests/overwriteProfile/overwriteExistingProfile.ts b/src/apps/agents/service/tests/overwriteProfile/overwriteExistingProfile.ts new file mode 100644 index 000000000..736a411a1 --- /dev/null +++ b/src/apps/agents/service/tests/overwriteProfile/overwriteExistingProfile.ts @@ -0,0 +1,54 @@ +import assertImmutableProfile from '../../../utils/assertImmutableProfile'; +import assertProfile from '../../../utils/assertProfile'; +import createImmutableProfile from '../../../utils/createImmutableProfile'; +import { + TEST_ACCOUNT_AGENT, + TEST_CONTENT, + TEST_IMMUTABLE_CONTENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OPENID_AGENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('overwriteProfile with existing model', () => { + setup(); + + it('should overwrite model when overwriting an existing model', async () => { + await overwriteProfile({}, TEST_CONTENT); + await overwriteProfile({}, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT); + }); + + it('should not overwrite non-matched models', async () => { + await overwriteProfile(); + await createImmutableProfile(); + await overwriteProfile(); + await assertImmutableProfile(); + }); + + it('should overwrite model when overwriting with mbox', async () => { + await overwriteProfile({ agent: TEST_MBOX_AGENT }, TEST_CONTENT); + await overwriteProfile({ agent: TEST_MBOX_AGENT }, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_MBOX_AGENT }); + }); + + it('should overwrite model when overwriting with mbox_sha1sum', async () => { + await overwriteProfile({ agent: TEST_MBOXSHA1_AGENT }, TEST_CONTENT); + await overwriteProfile({ agent: TEST_MBOXSHA1_AGENT }, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_MBOXSHA1_AGENT }); + }); + + it('should overwrite model when overwriting with openid', async () => { + await overwriteProfile({ agent: TEST_OPENID_AGENT }, TEST_CONTENT); + await overwriteProfile({ agent: TEST_OPENID_AGENT }, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_OPENID_AGENT }); + }); + + it('should overwrite model when overwriting with account', async () => { + await overwriteProfile({ agent: TEST_ACCOUNT_AGENT }, TEST_CONTENT); + await overwriteProfile({ agent: TEST_ACCOUNT_AGENT }, TEST_IMMUTABLE_CONTENT); + await assertProfile(TEST_IMMUTABLE_CONTENT, { agent: TEST_ACCOUNT_AGENT }); + }); +}); diff --git a/src/apps/agents/service/tests/overwriteProfile/overwriteNonExistingProfile.test.ts b/src/apps/agents/service/tests/overwriteProfile/overwriteNonExistingProfile.test.ts new file mode 100644 index 000000000..c79a5f72c --- /dev/null +++ b/src/apps/agents/service/tests/overwriteProfile/overwriteNonExistingProfile.test.ts @@ -0,0 +1,33 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { Warnings } from 'rulr'; +import JsonSyntaxError from '../../../errors/JsonSyntaxError'; +import assertProfile from '../../../utils/assertProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_INVALID_AGENT, + TEST_INVALID_JSON_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('overwriteProfile with non-existing model', () => { + setup(); + + it('should create when using valid agent', async () => { + await overwriteProfile(); + await assertProfile(TEST_CONTENT); + }); + + it('should throw warnings when using an invalid agent', async () => { + const promise = overwriteProfile({ agent: TEST_INVALID_AGENT }); + await assertError(Warnings, promise); + }); + + it('should throw warnings when using an invalid json content', async () => { + const promise = overwriteProfile({ + contentType: JSON_CONTENT_TYPE, + }, TEST_INVALID_JSON_CONTENT); + await assertError(JsonSyntaxError, promise); + }); +}); diff --git a/src/apps/agents/service/tests/overwriteProfile/overwriteOutsideClient.test.ts b/src/apps/agents/service/tests/overwriteProfile/overwriteOutsideClient.test.ts new file mode 100644 index 000000000..9f64457fe --- /dev/null +++ b/src/apps/agents/service/tests/overwriteProfile/overwriteOutsideClient.test.ts @@ -0,0 +1,31 @@ +import stringToStream from 'string-to-stream'; +import ClientModel from '../../../models/ClientModel'; +import assertProfile from '../../../utils/assertProfile'; +import { + TEST_CLIENT_OUTSIDE_ORG, + TEST_CLIENT_OUTSIDE_STORE, + TEST_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('overwriteProfile when outside client', () => { + setup(); + + const overwriteOutsideProfile = async (client: ClientModel) => { + const content = stringToStream('unused_content'); + await overwriteProfile({ client, content }); + }; + + it('should not overwrite existing model when using a different organisation', async () => { + await overwriteProfile(); + await overwriteOutsideProfile(TEST_CLIENT_OUTSIDE_ORG); + await assertProfile(TEST_CONTENT); + }); + + it('should not overwrite existing model when using a different store', async () => { + await overwriteProfile(); + await overwriteOutsideProfile(TEST_CLIENT_OUTSIDE_STORE); + await assertProfile(TEST_CONTENT); + }); +}); diff --git a/src/apps/agents/service/tests/overwriteProfile/overwriteWithEtags.test.ts b/src/apps/agents/service/tests/overwriteProfile/overwriteWithEtags.test.ts new file mode 100644 index 000000000..9f1fbea87 --- /dev/null +++ b/src/apps/agents/service/tests/overwriteProfile/overwriteWithEtags.test.ts @@ -0,0 +1,57 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import Conflict from '../../../errors/Conflict'; +import IfMatch from '../../../errors/IfMatch'; +import IfNoneMatch from '../../../errors/IfNoneMatch'; +import MaxEtags from '../../../errors/MaxEtags'; +import MissingEtags from '../../../errors/MissingEtags'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + TEST_CLIENT, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('overwriteProfile with etags', () => { + const service = setup(); + + it('should allow overwrites when using a correct etag', async () => { + await createTextProfile(); + const getProfileResult = await service.getProfile({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + profileId: TEST_PROFILE_ID, + }); + await overwriteProfile({ ifMatch: getProfileResult.etag, ifNoneMatch: undefined }); + }); + + it('should throw precondition error when using an incorrect ifMatch', async () => { + await createTextProfile(); + const promise = overwriteProfile({ ifMatch: 'incorrect_etag', ifNoneMatch: undefined }); + await assertError(IfMatch, promise); + }); + + it('should throw precondition error when using an incorrect ifNoneMatch', async () => { + await createTextProfile(); + const promise = overwriteProfile({ ifNoneMatch: '*' }); + await assertError(IfNoneMatch, promise); + }); + + it('should throw conflict error when not using ifMatch or ifNoneMatch', async () => { + await createTextProfile(); + const promise = overwriteProfile({ ifNoneMatch: undefined }); + await assertError(Conflict, promise); + }); + + it('should throw max etag error when using ifMatch and ifNoneMatch', async () => { + await createTextProfile(); + const promise = overwriteProfile({ ifMatch: 'incorrect_etag', ifNoneMatch: '*' }); + await assertError(MaxEtags, promise); + }); + + it('should throw missing etags error when not using ifMatch and ifNoneMatch', async () => { + const promise = overwriteProfile({ ifNoneMatch: undefined }); + await assertError(MissingEtags, promise); + }); +}); diff --git a/src/apps/agents/service/tests/overwriteProfile/overwriteWithScopes.test.ts b/src/apps/agents/service/tests/overwriteProfile/overwriteWithScopes.test.ts new file mode 100644 index 000000000..5db608d20 --- /dev/null +++ b/src/apps/agents/service/tests/overwriteProfile/overwriteWithScopes.test.ts @@ -0,0 +1,21 @@ +import Forbidden from 'jscommons/dist/errors/Forbidden'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { + TEST_INVALID_SCOPE_CLIENT, + TEST_VALID_SCOPE_CLIENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import overwriteProfile from './utils/overwriteProfile'; + +describe('overwriteProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + const promise = overwriteProfile({ client: TEST_INVALID_SCOPE_CLIENT }); + await assertError(Forbidden, promise); + }); + + it('should not throw an error when using valid scopes', async () => { + await overwriteProfile({ client: TEST_VALID_SCOPE_CLIENT }); + }); +}); diff --git a/src/apps/agents/service/tests/overwriteProfile/utils/overwriteProfile.ts b/src/apps/agents/service/tests/overwriteProfile/utils/overwriteProfile.ts new file mode 100644 index 000000000..4eb16fc57 --- /dev/null +++ b/src/apps/agents/service/tests/overwriteProfile/utils/overwriteProfile.ts @@ -0,0 +1,25 @@ +import stringToStream from 'string-to-stream'; +import OverwriteProfileOptions from '../../../../serviceFactory/options/OverwriteProfileOptions'; +import service from '../../../../utils/testService'; +import { + TEST_CLIENT, + TEST_CONTENT, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, + TEXT_CONTENT_TYPE, +} from '../../../../utils/testValues'; + +export default async ( + optsOverrides: Partial = {}, + content: string = TEST_CONTENT, +) => { + await service.overwriteProfile({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + content: stringToStream(content), + contentType: TEXT_CONTENT_TYPE, + ifNoneMatch: '*', + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/service/tests/patchProfile/patchExistingJson.test.ts b/src/apps/agents/service/tests/patchProfile/patchExistingJson.test.ts new file mode 100644 index 000000000..d20121b31 --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/patchExistingJson.test.ts @@ -0,0 +1,34 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import NonJsonObject from '../../../errors/NonJsonObject'; +import createJsonProfile from '../../../utils/createJsonProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_OBJECT_CONTENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; + +describe('patchProfile with existing JSON content', () => { + setup(); + + it('should error when patching with text content', async () => { + await createJsonProfile(); + const promise = patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should error when patching with JSON content', async () => { + await createJsonProfile(); + const promise = patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should error when patching with object content', async () => { + await createJsonProfile(); + const promise = patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); +}); diff --git a/src/apps/agents/service/tests/patchProfile/patchExistingObject.test.ts b/src/apps/agents/service/tests/patchProfile/patchExistingObject.test.ts new file mode 100644 index 000000000..1289fd6d5 --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/patchExistingObject.test.ts @@ -0,0 +1,74 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import NonJsonObject from '../../../errors/NonJsonObject'; +import assertImmutableProfile from '../../../utils/assertImmutableProfile'; +import assertProfile from '../../../utils/assertProfile'; +import createImmutableProfile from '../../../utils/createImmutableProfile'; +import createObjectProfile from '../../../utils/createObjectProfile'; +import { + JSON_CONTENT_TYPE, + TEST_ACCOUNT_AGENT, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_MBOX_AGENT, + TEST_MBOXSHA1_AGENT, + TEST_OBJECT_MERGED_CONTENT, + TEST_OBJECT_PATCH_CONTENT, + TEST_OPENID_AGENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; +import patchProfile from './utils/patchProfile'; + +describe('patchProfile with existing object content', () => { + setup(); + + it('should error when patching with text content', async () => { + await createObjectProfile(); + const promise = patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should error when patching with JSON content', async () => { + await createObjectProfile(); + const promise = patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should merge when patching with object content', async () => { + await createObjectProfile(); + await patchContent(TEST_OBJECT_PATCH_CONTENT, JSON_CONTENT_TYPE); + await assertProfile(TEST_OBJECT_MERGED_CONTENT); + }); + + it('should not patch existing models when patching a non-existing model', async () => { + await createObjectProfile(); + await createImmutableProfile(); + await patchProfile(); + await assertImmutableProfile(); + }); + + it('should merge when patching with mbox', async () => { + await createObjectProfile({ agent: TEST_MBOX_AGENT }); + await patchProfile({ agent: TEST_MBOX_AGENT }, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_MBOX_AGENT }); + }); + + it('should merge when patching with mbox_sha1sum', async () => { + await createObjectProfile({ agent: TEST_MBOXSHA1_AGENT }); + await patchProfile({ agent: TEST_MBOXSHA1_AGENT }, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_MBOXSHA1_AGENT }); + }); + + it('should merge when patching with openid', async () => { + await createObjectProfile({ agent: TEST_OPENID_AGENT }); + await patchProfile({ agent: TEST_OPENID_AGENT }, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_OPENID_AGENT }); + }); + + it('should merge when patching with account', async () => { + await createObjectProfile({ agent: TEST_ACCOUNT_AGENT }); + await patchProfile({ agent: TEST_ACCOUNT_AGENT }, TEST_OBJECT_PATCH_CONTENT); + await assertProfile(TEST_OBJECT_MERGED_CONTENT, { agent: TEST_ACCOUNT_AGENT }); + }); +}); diff --git a/src/apps/agents/service/tests/patchProfile/patchExistingText.test.ts b/src/apps/agents/service/tests/patchProfile/patchExistingText.test.ts new file mode 100644 index 000000000..aec0d5628 --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/patchExistingText.test.ts @@ -0,0 +1,34 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import NonJsonObject from '../../../errors/NonJsonObject'; +import createTextProfile from '../../../utils/createTextProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_JSON_CONTENT, + TEST_OBJECT_CONTENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; + +describe('patchProfile with existing text content', () => { + setup(); + + it('should error when patching with text content', async () => { + await createTextProfile(); + const promise = patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should error when patching with JSON content', async () => { + await createTextProfile(); + const promise = patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should error when patching with object content', async () => { + await createTextProfile(); + const promise = patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); +}); diff --git a/src/apps/agents/service/tests/patchProfile/patchNewContent.test.ts b/src/apps/agents/service/tests/patchProfile/patchNewContent.test.ts new file mode 100644 index 000000000..170f6a76c --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/patchNewContent.test.ts @@ -0,0 +1,48 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { Warnings } from 'rulr'; +import JsonSyntaxError from '../../../errors/JsonSyntaxError'; +import NonJsonObject from '../../../errors/NonJsonObject'; +import assertProfile from '../../../utils/assertProfile'; +import { + JSON_CONTENT_TYPE, + TEST_CONTENT, + TEST_INVALID_AGENT, + TEST_INVALID_JSON_CONTENT, + TEST_JSON_CONTENT, + TEST_OBJECT_CONTENT, + TEXT_CONTENT_TYPE, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchContent from './utils/patchContent'; +import patchProfile from './utils/patchProfile'; + +describe('patchProfile with new content', () => { + setup(); + + it('should error when patching with text content', async () => { + const promise = patchContent(TEST_CONTENT, TEXT_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should error when patching with JSON content', async () => { + const promise = patchContent(TEST_JSON_CONTENT, JSON_CONTENT_TYPE); + await assertError(NonJsonObject, promise); + }); + + it('should create when patching with object content', async () => { + await patchContent(TEST_OBJECT_CONTENT, JSON_CONTENT_TYPE); + await assertProfile(TEST_OBJECT_CONTENT); + }); + + it('should throw warnings when using an invalid agent', async () => { + const promise = patchProfile({ + agent: TEST_INVALID_AGENT, + }); + await assertError(Warnings, promise); + }); + + it('should throw warnings when using an invalid json content', async () => { + const promise = patchContent(TEST_INVALID_JSON_CONTENT, JSON_CONTENT_TYPE); + await assertError(JsonSyntaxError, promise); + }); +}); diff --git a/src/apps/agents/service/tests/patchProfile/patchOutsideClient.test.ts b/src/apps/agents/service/tests/patchProfile/patchOutsideClient.test.ts new file mode 100644 index 000000000..23f4a3e84 --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/patchOutsideClient.test.ts @@ -0,0 +1,29 @@ +import ClientModel from '../../../models/ClientModel'; +import assertProfile from '../../../utils/assertProfile'; +import { + TEST_CLIENT_OUTSIDE_ORG, + TEST_CLIENT_OUTSIDE_STORE, + TEST_OBJECT_CONTENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchProfile from './utils/patchProfile'; + +describe('patchProfile when outside client', () => { + setup(); + + const patchOutsideProfile = async (client: ClientModel) => { + await patchProfile({ client }, '{"bar":2}'); + }; + + it('should not overwrite existing model when using a different organisation', async () => { + await patchProfile(); + await patchOutsideProfile(TEST_CLIENT_OUTSIDE_ORG); + await assertProfile(TEST_OBJECT_CONTENT); + }); + + it('should not overwrite existing model when using a different store', async () => { + await patchProfile(); + await patchOutsideProfile(TEST_CLIENT_OUTSIDE_STORE); + await assertProfile(TEST_OBJECT_CONTENT); + }); +}); diff --git a/src/apps/agents/service/tests/patchProfile/patchWithEtags.test.ts b/src/apps/agents/service/tests/patchProfile/patchWithEtags.test.ts new file mode 100644 index 000000000..4280c52b5 --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/patchWithEtags.test.ts @@ -0,0 +1,41 @@ +import assertError from 'jscommons/dist/tests/utils/assertError'; +import IfMatch from '../../../errors/IfMatch'; +import IfNoneMatch from '../../../errors/IfNoneMatch'; +import MaxEtags from '../../../errors/MaxEtags'; +import createObjectProfile from '../../../utils/createObjectProfile'; +import getTestProfile from '../../../utils/getTestProfile'; +import setup from '../utils/setup'; +import patchProfile from './utils/patchProfile'; + +describe('patchProfile with etags', () => { + setup(); + + it('should allow patches when using a correct etag', async () => { + await createObjectProfile(); + const getProfileResult = await getTestProfile(); + await patchProfile({ ifMatch: getProfileResult.etag }); + }); + + it('should throw precondition error when using an incorrect ifMatch', async () => { + await createObjectProfile(); + const promise = patchProfile({ ifMatch: 'incorrect_etag' }); + await assertError(IfMatch, promise); + }); + + it('should throw precondition error when using an incorrect ifNoneMatch', async () => { + await createObjectProfile(); + const promise = patchProfile({ ifNoneMatch: '*' }); + await assertError(IfNoneMatch, promise); + }); + + it('should allow patch when not using an ifMatch or ifNoneMatch', async () => { + await createObjectProfile(); + await patchProfile(); + }); + + it('should throw max etag error when using ifMatch and ifNoneMatch', async () => { + await createObjectProfile(); + const promise = patchProfile({ ifMatch: 'incorrect_etag', ifNoneMatch: '*' }); + await assertError(MaxEtags, promise); + }); +}); diff --git a/src/apps/agents/service/tests/patchProfile/patchWithScopes.test.ts b/src/apps/agents/service/tests/patchProfile/patchWithScopes.test.ts new file mode 100644 index 000000000..f1d2618ae --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/patchWithScopes.test.ts @@ -0,0 +1,25 @@ +import Forbidden from 'jscommons/dist/errors/Forbidden'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import { + TEST_INVALID_SCOPE_CLIENT, + TEST_VALID_SCOPE_CLIENT, +} from '../../../utils/testValues'; +import setup from '../utils/setup'; +import patchProfile from './utils/patchProfile'; + +describe('patchProfile with scopes', () => { + setup(); + + it('should throw forbidden error when using invalid scope', async () => { + const promise = patchProfile({ + client: TEST_INVALID_SCOPE_CLIENT, + }); + await assertError(Forbidden, promise); + }); + + it('should not throw an error when using valid scopes', async () => { + await patchProfile({ + client: TEST_VALID_SCOPE_CLIENT, + }); + }); +}); diff --git a/src/apps/agents/service/tests/patchProfile/utils/patchContent.ts b/src/apps/agents/service/tests/patchProfile/utils/patchContent.ts new file mode 100644 index 000000000..3bb3ff0ce --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/utils/patchContent.ts @@ -0,0 +1,5 @@ +import patchProfile from './patchProfile'; + +export default async (content: string, contentType: string) => { + await patchProfile({ contentType }, content); +}; diff --git a/src/apps/agents/service/tests/patchProfile/utils/patchProfile.ts b/src/apps/agents/service/tests/patchProfile/utils/patchProfile.ts new file mode 100644 index 000000000..370aa7a2f --- /dev/null +++ b/src/apps/agents/service/tests/patchProfile/utils/patchProfile.ts @@ -0,0 +1,24 @@ +import stringToStream from 'string-to-stream'; +import PatchProfileOptions from '../../../../serviceFactory/options/PatchProfileOptions'; +import service from '../../../../utils/testService'; +import { + JSON_CONTENT_TYPE, + TEST_CLIENT, + TEST_MBOX_AGENT, + TEST_OBJECT_CONTENT, + TEST_PROFILE_ID, +} from '../../../../utils/testValues'; + +export default async ( + optsOverrides: Partial = {}, + content: string = TEST_OBJECT_CONTENT, +) => { + await service.patchProfile({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + content: stringToStream(content), + contentType: JSON_CONTENT_TYPE, + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/service/tests/utils/setup.ts b/src/apps/agents/service/tests/utils/setup.ts new file mode 100644 index 000000000..d465bdf28 --- /dev/null +++ b/src/apps/agents/service/tests/utils/setup.ts @@ -0,0 +1,9 @@ +import setupService from 'jscommons/dist/tests/utils/setupService'; +import Service from '../../../serviceFactory/Service'; +import service from '../../../utils/testService'; + +const setup = setupService(service); + +export default (): Service => { + return setup(); +}; diff --git a/src/apps/agents/service/tests/validateAgent/validateAgent.test.ts b/src/apps/agents/service/tests/validateAgent/validateAgent.test.ts new file mode 100644 index 000000000..478d38acd --- /dev/null +++ b/src/apps/agents/service/tests/validateAgent/validateAgent.test.ts @@ -0,0 +1,88 @@ +import * as assert from 'assert'; +import { Warnings } from 'rulr'; +import validateAgent from '../../utils/validateAgent'; + +describe('validateAgent', () => { + const assertWarnings = (agent: any) => { + try { + validateAgent(agent); + } catch (err) { + const actualConstructor = err.constructor; + assert.equal(actualConstructor, Warnings); + } + }; + + it('should throw an error when using an invalid mbox value', () => { + assertWarnings({ mbox: 'test@example.org' }); + }); + + it('should throw an error when using an invalid mbox_sha1sum value', () => { + assertWarnings({ mbox_sha1sum: 'test@example.org' }); + }); + + it('should throw an error when using an invalid openid value', () => { + assertWarnings({ openid: 'www.example.org' }); + }); + + it('should throw an error when using an invalid homePage value', () => { + assertWarnings({ account: { + homePage: 'www.example.org', + name: 'dummy_account_name', + } }); + }); + + it('should throw an error when using an invalid name type', () => { + assertWarnings({ account: { + homePage: 'http://www.example.org', + name: 10, + } }); + }); + + it('should throw an error when using an invalid homePage value and name type', () => { + assertWarnings({ account: { + homePage: 'www.example.org', + name: 10, + } }); + }); + + it('should throw an error when using an invalid mbox type', () => { + assertWarnings({ mbox: 10 }); + }); + + it('should throw an error when using an invalid mbox_sha1sum type', () => { + assertWarnings({ mbox_sha1sum: 10 }); + }); + + it('should throw an error when using an invalid openid type', () => { + assertWarnings({ openid: 10 }); + }); + + it('should throw an error when using an invalid homePage type', () => { + assertWarnings({ account: { + homePage: 10, + name: 'dummy_account_name', + } }); + }); + + it('should throw an error when using an invalid homePage type and name type', () => { + assertWarnings({ account: { + homePage: 10, + name: 10, + } }); + }); + + it('should throw an error when using too many IFIs', () => { + assertWarnings({ + mbox: 'mailto:test@example.org', + openid: 'http://www.example.org', + }); + }); + + it('should throw an error when using no IFIs', () => { + assertWarnings({}); + }); + + it('should throw an error when using an invalid IFI', () => { + assertWarnings({ foo: 'bar' }); + }); +}); diff --git a/src/apps/agents/service/utils/checkProfileReadScopes.ts b/src/apps/agents/service/utils/checkProfileReadScopes.ts new file mode 100644 index 000000000..df354c698 --- /dev/null +++ b/src/apps/agents/service/utils/checkProfileReadScopes.ts @@ -0,0 +1,6 @@ +import checkScopes from 'jscommons/dist/service/utils/checkScopes'; +import { PROFILE_READ_SCOPES } from '../../utils/scopes'; + +export default (scopes: string[]) => { + checkScopes(PROFILE_READ_SCOPES, scopes); +}; diff --git a/src/apps/agents/service/utils/checkProfileWriteScopes.ts b/src/apps/agents/service/utils/checkProfileWriteScopes.ts new file mode 100644 index 000000000..c02c3f3a2 --- /dev/null +++ b/src/apps/agents/service/utils/checkProfileWriteScopes.ts @@ -0,0 +1,6 @@ +import checkScopes from 'jscommons/dist/service/utils/checkScopes'; +import { PROFILE_WRITE_SCOPES } from '../../utils/scopes'; + +export default (scopes: string[]) => { + checkScopes(PROFILE_WRITE_SCOPES, scopes); +}; diff --git a/src/apps/agents/service/utils/createEtag.ts b/src/apps/agents/service/utils/createEtag.ts new file mode 100644 index 000000000..d806cacfa --- /dev/null +++ b/src/apps/agents/service/utils/createEtag.ts @@ -0,0 +1,8 @@ +import sha1 from 'sha1'; +import { v4 as uuid } from 'uuid'; + +export default () => { + const id = uuid(); + const timestamp = (new Date()).toISOString(); + return sha1(`${id}-${timestamp}`); +}; diff --git a/src/apps/agents/service/utils/validateAgent.ts b/src/apps/agents/service/utils/validateAgent.ts new file mode 100644 index 000000000..9a300e5cf --- /dev/null +++ b/src/apps/agents/service/utils/validateAgent.ts @@ -0,0 +1,31 @@ +import * as xapi from '@learninglocker/xapi-validation/dist/factory'; +import IfiCountWarning from '@learninglocker/xapi-validation/dist/warnings/IfiCountWarning'; +import NoIfiWarning from '@learninglocker/xapi-validation/dist/warnings/NoIfiWarning'; +import { pick } from 'lodash'; +import * as rulr from 'rulr'; + +const rule = rulr.maybe(rulr.composeRules([ + rulr.restrictToSchema({ + account: rulr.optional(xapi.account), + mbox: rulr.optional(xapi.mailto), + mbox_sha1sum: rulr.optional(xapi.sha1), + name: rulr.optional(xapi.stringValue), + objectType: rulr.optional(xapi.stringValue), + openid: rulr.optional(xapi.iri), + }), + (data, path) => { + const trimmedAgent = pick(data, ['account', 'mbox', 'mbox_sha1sum', 'openid']); + const keys = Object.keys(trimmedAgent); + if (keys.length > 1) { + return [new IfiCountWarning(data, path, keys)]; + } + if (keys.length === 0) { + return [new NoIfiWarning(data, path)]; + } + return []; + }, +])); + +export default (data: any) => { + return rule(data, ['agent']); +}; diff --git a/src/apps/agents/service/utils/validateSince.ts b/src/apps/agents/service/utils/validateSince.ts new file mode 100644 index 000000000..50113da12 --- /dev/null +++ b/src/apps/agents/service/utils/validateSince.ts @@ -0,0 +1,6 @@ +import * as xapi from '@learninglocker/xapi-validation/dist/factory'; +import * as rulr from 'rulr'; + +export default (data: string) => { + return rulr.maybe(xapi.timestamp)(data, ['since']); +}; diff --git a/src/apps/agents/serviceFactory/Service.ts b/src/apps/agents/serviceFactory/Service.ts new file mode 100644 index 000000000..d487aa4a7 --- /dev/null +++ b/src/apps/agents/serviceFactory/Service.ts @@ -0,0 +1,24 @@ +import CommonService from 'jscommons/dist/serviceFactory/Service'; +import DeleteProfileOptions from './options/DeleteProfileOptions'; +import GetClientOptions from './options/GetClientOptions'; +import GetFullAgentOptions from './options/GetFullAgentOptions'; +import GetProfileOptions from './options/GetProfileOptions'; +import GetProfilesOptions from './options/GetProfilesOptions'; +import OverwriteProfileOptions from './options/OverwriteProfileOptions'; +import PatchProfileOptions from './options/PatchProfileOptions'; +import GetClientResult from './results/GetClientResult'; +import GetFullAgentResult from './results/GetFullAgentResult'; +import GetProfileResult from './results/GetProfileResult'; +import GetProfilesResult from './results/GetProfilesResult'; + +interface Service extends CommonService { + readonly deleteProfile: (opts: DeleteProfileOptions) => Promise; + readonly getClient: (opts: GetClientOptions) => Promise; + readonly getFullAgent: (opts: GetFullAgentOptions) => Promise; + readonly getProfile: (opts: GetProfileOptions) => Promise; + readonly getProfiles: (opts: GetProfilesOptions) => Promise; + readonly overwriteProfile: (opts: OverwriteProfileOptions) => Promise; + readonly patchProfile: (opts: PatchProfileOptions) => Promise; +} + +export default Service; diff --git a/src/apps/agents/serviceFactory/index.ts b/src/apps/agents/serviceFactory/index.ts new file mode 100644 index 000000000..5e06bec82 --- /dev/null +++ b/src/apps/agents/serviceFactory/index.ts @@ -0,0 +1,11 @@ +import repoFactory from '../repoFactory'; +import service from '../service'; +import Service from './Service'; + +export default (): Service => { + const repoFacade = repoFactory(); + + return service({ + repo: repoFacade, + }); +}; diff --git a/src/apps/agents/serviceFactory/options/DeleteProfileOptions.ts b/src/apps/agents/serviceFactory/options/DeleteProfileOptions.ts new file mode 100644 index 000000000..5c085b09c --- /dev/null +++ b/src/apps/agents/serviceFactory/options/DeleteProfileOptions.ts @@ -0,0 +1,9 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +export default interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly profileId: string; + readonly ifMatch?: string; +} diff --git a/src/apps/agents/serviceFactory/options/GetClientOptions.ts b/src/apps/agents/serviceFactory/options/GetClientOptions.ts new file mode 100644 index 000000000..33178bd83 --- /dev/null +++ b/src/apps/agents/serviceFactory/options/GetClientOptions.ts @@ -0,0 +1,3 @@ +export default interface Options { + readonly authToken: string; +} diff --git a/src/apps/agents/serviceFactory/options/GetFullAgentOptions.ts b/src/apps/agents/serviceFactory/options/GetFullAgentOptions.ts new file mode 100644 index 000000000..0cb90548b --- /dev/null +++ b/src/apps/agents/serviceFactory/options/GetFullAgentOptions.ts @@ -0,0 +1,7 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +export default interface Options { + readonly agent: Agent; + readonly client: ClientModel; +} diff --git a/src/apps/agents/serviceFactory/options/GetProfileOptions.ts b/src/apps/agents/serviceFactory/options/GetProfileOptions.ts new file mode 100644 index 000000000..678ce3f1b --- /dev/null +++ b/src/apps/agents/serviceFactory/options/GetProfileOptions.ts @@ -0,0 +1,8 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +export default interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly profileId: string; +} diff --git a/src/apps/agents/serviceFactory/options/GetProfilesOptions.ts b/src/apps/agents/serviceFactory/options/GetProfilesOptions.ts new file mode 100644 index 000000000..d9a678ac8 --- /dev/null +++ b/src/apps/agents/serviceFactory/options/GetProfilesOptions.ts @@ -0,0 +1,8 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +export default interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly since?: string; +} diff --git a/src/apps/agents/serviceFactory/options/OverwriteProfileOptions.ts b/src/apps/agents/serviceFactory/options/OverwriteProfileOptions.ts new file mode 100644 index 000000000..48db90345 --- /dev/null +++ b/src/apps/agents/serviceFactory/options/OverwriteProfileOptions.ts @@ -0,0 +1,12 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +export default interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly content: NodeJS.ReadableStream; + readonly contentType: string; + readonly ifMatch?: string; + readonly ifNoneMatch?: string; + readonly profileId: string; +} diff --git a/src/apps/agents/serviceFactory/options/PatchProfileOptions.ts b/src/apps/agents/serviceFactory/options/PatchProfileOptions.ts new file mode 100644 index 000000000..48db90345 --- /dev/null +++ b/src/apps/agents/serviceFactory/options/PatchProfileOptions.ts @@ -0,0 +1,12 @@ +import Agent from '../../models/Agent'; +import ClientModel from '../../models/ClientModel'; + +export default interface Options { + readonly agent: Agent; + readonly client: ClientModel; + readonly content: NodeJS.ReadableStream; + readonly contentType: string; + readonly ifMatch?: string; + readonly ifNoneMatch?: string; + readonly profileId: string; +} diff --git a/src/apps/agents/serviceFactory/results/GetClientResult.ts b/src/apps/agents/serviceFactory/results/GetClientResult.ts new file mode 100644 index 000000000..30d5db4a0 --- /dev/null +++ b/src/apps/agents/serviceFactory/results/GetClientResult.ts @@ -0,0 +1,5 @@ +import ClientModel from '../../models/ClientModel'; + +export default interface Result { + readonly client: ClientModel; +} diff --git a/src/apps/agents/serviceFactory/results/GetFullAgentResult.ts b/src/apps/agents/serviceFactory/results/GetFullAgentResult.ts new file mode 100644 index 000000000..f8839bd46 --- /dev/null +++ b/src/apps/agents/serviceFactory/results/GetFullAgentResult.ts @@ -0,0 +1,12 @@ +import Account from '../../models/Account'; + +interface Result { + readonly objectType: string; + readonly name: string[]; + readonly mbox: string[]; + readonly mbox_sha1sum: string[]; + readonly openid: string[]; + readonly account: Account[]; +} + +export default Result; diff --git a/src/apps/agents/serviceFactory/results/GetProfileResult.ts b/src/apps/agents/serviceFactory/results/GetProfileResult.ts new file mode 100644 index 000000000..dbbd57250 --- /dev/null +++ b/src/apps/agents/serviceFactory/results/GetProfileResult.ts @@ -0,0 +1,6 @@ +export default interface Result { + readonly content: NodeJS.ReadableStream; + readonly contentType: string; + readonly etag: string; + readonly updatedAt: Date; +} diff --git a/src/apps/agents/serviceFactory/results/GetProfilesResult.ts b/src/apps/agents/serviceFactory/results/GetProfilesResult.ts new file mode 100644 index 000000000..94a154f35 --- /dev/null +++ b/src/apps/agents/serviceFactory/results/GetProfilesResult.ts @@ -0,0 +1,3 @@ +export default interface Result { + readonly profileIds: string[]; +} diff --git a/src/apps/agents/testAuthRepo/Config.ts b/src/apps/agents/testAuthRepo/Config.ts new file mode 100644 index 000000000..ba834405e --- /dev/null +++ b/src/apps/agents/testAuthRepo/Config.ts @@ -0,0 +1,3 @@ +type Config = object; + +export default Config; diff --git a/src/apps/agents/testAuthRepo/getClient.ts b/src/apps/agents/testAuthRepo/getClient.ts new file mode 100644 index 000000000..d232acdc3 --- /dev/null +++ b/src/apps/agents/testAuthRepo/getClient.ts @@ -0,0 +1,43 @@ +import NoModel from 'jscommons/dist/errors/NoModel'; +import ExpiredClientError from '../errors/ExpiredClientError'; +import UntrustedClientError from '../errors/UntrustedClientError'; +import GetClientOptions from '../repoFactory/options/GetClientOptions'; +import GetClientResult from '../repoFactory/results/GetClientResult'; +import { + TEST_CLIENT, + TEST_CLIENT_OUTSIDE_ORG, + TEST_CLIENT_OUTSIDE_STORE, + TEST_EXPIRED_ORG_TOKEN, + TEST_INVALID_SCOPE_CLIENT, + TEST_INVALID_SCOPE_TOKEN, + TEST_MISSING_TOKEN, + TEST_OUTSIDE_ORG_TOKEN, + TEST_OUTSIDE_STORE_TOKEN, + TEST_UNTRUSTED_TOKEN, + TEST_VALID_SCOPE_CLIENT, + TEST_VALID_SCOPE_TOKEN, +} from '../utils/testValues'; +import Config from './Config'; + +export default (_config: Config) => { + return async (opts: GetClientOptions): Promise => { + switch (opts.authToken) { + case TEST_INVALID_SCOPE_TOKEN: + return { client: TEST_INVALID_SCOPE_CLIENT }; + case TEST_VALID_SCOPE_TOKEN: + return { client: TEST_VALID_SCOPE_CLIENT }; + case TEST_OUTSIDE_ORG_TOKEN: + return { client: TEST_CLIENT_OUTSIDE_ORG }; + case TEST_OUTSIDE_STORE_TOKEN: + return { client: TEST_CLIENT_OUTSIDE_STORE }; + case TEST_UNTRUSTED_TOKEN: + throw new UntrustedClientError(); + case TEST_EXPIRED_ORG_TOKEN: + throw new ExpiredClientError(); + case TEST_MISSING_TOKEN: + throw new NoModel('Client'); + default: + return { client: TEST_CLIENT }; + } + }; +}; diff --git a/src/apps/agents/testAuthRepo/index.ts b/src/apps/agents/testAuthRepo/index.ts new file mode 100644 index 000000000..fa66ea3e6 --- /dev/null +++ b/src/apps/agents/testAuthRepo/index.ts @@ -0,0 +1,9 @@ +import AuthRepo from '../repoFactory/AuthRepo'; +import Config from './Config'; +import getClient from './getClient'; + +export default (config: Config): AuthRepo => { + return { + getClient: getClient(config), + }; +}; diff --git a/src/apps/agents/tests/getFileExtension.test.ts b/src/apps/agents/tests/getFileExtension.test.ts new file mode 100644 index 000000000..f91aab024 --- /dev/null +++ b/src/apps/agents/tests/getFileExtension.test.ts @@ -0,0 +1,21 @@ +import * as assert from 'assert'; +import { jsonContentType } from '../utils/constants'; +import getFileExtension from '../utils/getFileExtension'; + +describe('getFileExtension', () => { + + it('should return json on application/json', async () => { + const ext = getFileExtension(jsonContentType); + assert.equal(ext, 'json'); + }); + + it('should return known extension', async () => { + const ext = getFileExtension('text/plain'); + assert.equal(ext, 'txt'); + }); + + it('should return `bin` on unknown extension', async () => { + const ext = getFileExtension('ht2/binary'); + assert.equal(ext, 'bin'); + }); +}); diff --git a/src/apps/agents/translatorFactory/Translator.ts b/src/apps/agents/translatorFactory/Translator.ts new file mode 100644 index 000000000..e050b8048 --- /dev/null +++ b/src/apps/agents/translatorFactory/Translator.ts @@ -0,0 +1,28 @@ +import TypeWarning from '@learninglocker/xapi-validation/dist/warnings/TypeWarning'; +import CommonTranslator from 'jscommons/dist/translatorFactory/Translator'; +import Conflict from '../errors/Conflict'; +import ExpiredClientError from '../errors/ExpiredClientError'; +import IfMatch from '../errors/IfMatch'; +import IfNoneMatch from '../errors/IfNoneMatch'; +import InvalidMethod from '../errors/InvalidMethod'; +import JsonSyntaxError from '../errors/JsonSyntaxError'; +import MaxEtags from '../errors/MaxEtags'; +import MissingEtags from '../errors/MissingEtags'; +import NonJsonObject from '../errors/NonJsonObject'; +import UntrustedClientError from '../errors/UntrustedClientError'; + +interface Translator extends CommonTranslator { + readonly conflictError: (err: Conflict) => string; + readonly expiredClientError: (err: ExpiredClientError) => string; + readonly ifMatchError: (err: IfMatch) => string; + readonly ifNoneMatchError: (err: IfNoneMatch) => string; + readonly invalidMethodError: (err: InvalidMethod) => string; + readonly jsonSyntaxError: (err: JsonSyntaxError) => string; + readonly maxEtagsError: (err: MaxEtags) => string; + readonly missingEtagsError: (err: MissingEtags) => string; + readonly nonJsonObjectError: (err: NonJsonObject) => string; + readonly untrustedClientError: (err: UntrustedClientError) => string; + readonly xapiTypeWarning: (err: TypeWarning) => string; +} + +export default Translator; diff --git a/src/apps/agents/translatorFactory/en.ts b/src/apps/agents/translatorFactory/en.ts new file mode 100644 index 000000000..a60c5882e --- /dev/null +++ b/src/apps/agents/translatorFactory/en.ts @@ -0,0 +1,41 @@ +import commonTranslator from 'jscommons/dist/translatorFactory/en'; +import stringPath from 'jscommons/dist/translatorFactory/utils/stringPath'; +import Translator from './Translator'; + +const translator: Translator = { + conflictError: () => ( + 'Get the profile to retrieve the Etag, then set the If-Match header to the Etag' + ), + expiredClientError: () => `Your organisation has expired`, + ifMatchError: () => ( + 'IfMatch does not match Etag because a modification has been made since it was retrieved' + ), + ifNoneMatchError: () => ( + 'IfNoneMatch was used to detect that the resource was already present' + ), + invalidMethodError: (err) => ( + `Method (${err.method}) is invalid for alternate request syntax` + ), + jsonSyntaxError: (err) => { + const path = stringPath(err.path); + return `Expected valid JSON in ${path}`; + }, + maxEtagsError: () => ( + 'IfMatch and IfNoneMatch cannot be used at the same time' + ), + missingEtagsError: () => ( + 'IfMatch and IfNoneMatch header are missing' + ), + nonJsonObjectError: () => ( + 'Expected a JSON object to be provided and stored (if it exists)' + ), + untrustedClientError: () => 'Your client has been disabled', + xapiTypeWarning: (warning) => { + const path = stringPath(warning.path); + const dataString = JSON.stringify(warning.data); + return `Expected ${warning.typeName} in ${path}. Received '${dataString}'`; + }, + ...commonTranslator, +}; + +export default translator; diff --git a/src/apps/agents/translatorFactory/index.ts b/src/apps/agents/translatorFactory/index.ts new file mode 100644 index 000000000..c257da671 --- /dev/null +++ b/src/apps/agents/translatorFactory/index.ts @@ -0,0 +1,6 @@ +import en from './en'; +import Translator from './Translator'; + +export default (): Translator => { + return en; +}; diff --git a/src/apps/agents/utils/assertDeleted.ts b/src/apps/agents/utils/assertDeleted.ts new file mode 100644 index 000000000..d626fcc2c --- /dev/null +++ b/src/apps/agents/utils/assertDeleted.ts @@ -0,0 +1,16 @@ +import * as assert from 'assert'; +import NoModel from 'jscommons/dist/errors/NoModel'; +import assertError from 'jscommons/dist/tests/utils/assertError'; +import GetProfilesOptions from '../serviceFactory/options/GetProfilesOptions'; +import getTestProfile from './getTestProfile'; +import getTestProfiles from './getTestProfiles'; + +export default async (optsOverrides: Partial = {}) => { + // Asserts that the agent has no profiles. + const getProfilesResult = await getTestProfiles(optsOverrides); + assert.deepEqual([], getProfilesResult.profileIds); + + // Asserts that the profile does not exist. + const getProfilePromise = getTestProfile(optsOverrides); + await assertError(NoModel, getProfilePromise); +}; diff --git a/src/apps/agents/utils/assertImmutableProfile.ts b/src/apps/agents/utils/assertImmutableProfile.ts new file mode 100644 index 000000000..943bf5301 --- /dev/null +++ b/src/apps/agents/utils/assertImmutableProfile.ts @@ -0,0 +1,33 @@ +import * as assert from 'assert'; +import streamToString from 'stream-to-string'; +import service from './testService'; +import { + TEST_CLIENT, + TEST_IMMUTABLE_AGENT, + TEST_IMMUTABLE_CONTENT, + TEST_PROFILE_ID, +} from './testValues'; + +export default async () => { + const expectedProfileIds = [TEST_PROFILE_ID]; + + // Checks the profileIds. + const profilesResult = await service.getProfiles({ + agent: TEST_IMMUTABLE_AGENT, + client: TEST_CLIENT, + }); + const actualProfileIds = profilesResult.profileIds; + assert.deepEqual(actualProfileIds, expectedProfileIds); + + // Checks the content. + const agentProfileResult = await service.getProfile({ + agent: TEST_IMMUTABLE_AGENT, + client: TEST_CLIENT, + profileId: TEST_PROFILE_ID, + }); + const actualContent = await streamToString(agentProfileResult.content); + assert.equal(actualContent, TEST_IMMUTABLE_CONTENT); + assert.equal(agentProfileResult.contentType.constructor, String); + assert.equal(agentProfileResult.updatedAt.constructor, Date); + assert.equal(agentProfileResult.etag.constructor, String); +}; diff --git a/src/apps/agents/utils/assertProfile.ts b/src/apps/agents/utils/assertProfile.ts new file mode 100644 index 000000000..86549eb2a --- /dev/null +++ b/src/apps/agents/utils/assertProfile.ts @@ -0,0 +1,23 @@ +import * as assert from 'assert'; +import streamToString from 'stream-to-string'; +import GetProfilesOptions from '../serviceFactory/options/GetProfilesOptions'; +import getTestProfile from './getTestProfile'; +import getTestProfiles from './getTestProfiles'; +import { TEST_PROFILE_ID } from './testValues'; + +export default async (content: string, optsOverrides: Partial = {}) => { + const expectedProfileIds = [TEST_PROFILE_ID]; + + // Checks the profileIds. + const profilesResult = await getTestProfiles(optsOverrides); + const actualProfileIds = profilesResult.profileIds; + assert.deepEqual(actualProfileIds, expectedProfileIds); + + // Checks the content. + const agentProfileResult = await getTestProfile(optsOverrides); + const actualContent = await streamToString(agentProfileResult.content); + assert.equal(actualContent, content); + assert.equal(agentProfileResult.contentType.constructor, String); + assert.equal(agentProfileResult.updatedAt.constructor, Date); + assert.equal(agentProfileResult.etag.constructor, String); +}; diff --git a/src/apps/agents/utils/constants.ts b/src/apps/agents/utils/constants.ts new file mode 100644 index 000000000..24593802b --- /dev/null +++ b/src/apps/agents/utils/constants.ts @@ -0,0 +1,3 @@ +export const xapiHeaderVersion = '1.0.3'; +export const jsonContentType = 'application/json'; +export const route = '/xAPI/agents'; diff --git a/src/apps/agents/utils/createImmutableProfile.ts b/src/apps/agents/utils/createImmutableProfile.ts new file mode 100644 index 000000000..218ca2a3c --- /dev/null +++ b/src/apps/agents/utils/createImmutableProfile.ts @@ -0,0 +1,10 @@ +import stringToStream from 'string-to-stream'; +import createTextProfile from './createTextProfile'; +import { TEST_IMMUTABLE_AGENT, TEST_IMMUTABLE_CONTENT } from './testValues'; + +export default async () => { + await createTextProfile({ + agent: TEST_IMMUTABLE_AGENT, + content: stringToStream(TEST_IMMUTABLE_CONTENT), + }); +}; diff --git a/src/apps/agents/utils/createJsonProfile.ts b/src/apps/agents/utils/createJsonProfile.ts new file mode 100644 index 000000000..7a1f5c706 --- /dev/null +++ b/src/apps/agents/utils/createJsonProfile.ts @@ -0,0 +1,12 @@ +import stringToStream from 'string-to-stream'; +import OverwriteProfileOptions from '../serviceFactory/options/OverwriteProfileOptions'; +import createTextProfile from './createTextProfile'; +import { JSON_CONTENT_TYPE, TEST_JSON_CONTENT } from './testValues'; + +export default async (optsOverrides: Partial = {}) => { + await createTextProfile({ + content: stringToStream(TEST_JSON_CONTENT), + contentType: JSON_CONTENT_TYPE, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/utils/createObjectProfile.ts b/src/apps/agents/utils/createObjectProfile.ts new file mode 100644 index 000000000..0a9eac1b0 --- /dev/null +++ b/src/apps/agents/utils/createObjectProfile.ts @@ -0,0 +1,11 @@ +import stringToStream from 'string-to-stream'; +import OverwriteProfileOptions from '../serviceFactory/options/OverwriteProfileOptions'; +import createJsonProfile from './createJsonProfile'; +import { TEST_OBJECT_CONTENT } from './testValues'; + +export default async (optsOverrides: Partial = {}) => { + await createJsonProfile({ + content: stringToStream(TEST_OBJECT_CONTENT), + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/utils/createTextProfile.ts b/src/apps/agents/utils/createTextProfile.ts new file mode 100644 index 000000000..404166375 --- /dev/null +++ b/src/apps/agents/utils/createTextProfile.ts @@ -0,0 +1,22 @@ +import stringToStream from 'string-to-stream'; +import OverwriteProfileOptions from '../serviceFactory/options/OverwriteProfileOptions'; +import service from './testService'; +import { + TEST_CLIENT, + TEST_CONTENT, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, + TEXT_CONTENT_TYPE, +} from './testValues'; + +export default async (optsOverrides: Partial = {}) => { + await service.overwriteProfile({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + content: stringToStream(TEST_CONTENT), + contentType: TEXT_CONTENT_TYPE, + ifNoneMatch: '*', + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/utils/getFileExtension.ts b/src/apps/agents/utils/getFileExtension.ts new file mode 100644 index 000000000..14430a9dc --- /dev/null +++ b/src/apps/agents/utils/getFileExtension.ts @@ -0,0 +1,13 @@ +import { extension } from 'mime-types'; +import { jsonContentType } from '../utils/constants'; + +export default (contentType: string) => { + if (contentType === jsonContentType) { + return 'json'; + } + const ext = extension(contentType); + if (ext === false) { + return 'bin'; + } + return ext; +}; diff --git a/src/apps/agents/utils/getStorageDir.ts b/src/apps/agents/utils/getStorageDir.ts new file mode 100644 index 000000000..de460ebc7 --- /dev/null +++ b/src/apps/agents/utils/getStorageDir.ts @@ -0,0 +1,12 @@ +import { join } from 'path'; + +export interface GetStorageDirOptions { + readonly subfolder?: string; + readonly lrs_id: string; +} + +export default (opts: GetStorageDirOptions) => join( + ...(opts.subfolder !== undefined ? [opts.subfolder] : []), + opts.lrs_id, + 'agentProfiles', +); diff --git a/src/apps/agents/utils/getTestProfile.ts b/src/apps/agents/utils/getTestProfile.ts new file mode 100644 index 000000000..f8b5d03d9 --- /dev/null +++ b/src/apps/agents/utils/getTestProfile.ts @@ -0,0 +1,17 @@ +import GetProfileOptions from '../serviceFactory/options/GetProfileOptions'; +import GetProfileResult from '../serviceFactory/results/GetProfileResult'; +import service from './testService'; +import { + TEST_CLIENT, + TEST_MBOX_AGENT, + TEST_PROFILE_ID, +} from './testValues'; + +export default (optsOverrides: Partial = {}): Promise => { + return service.getProfile({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + profileId: TEST_PROFILE_ID, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/utils/getTestProfiles.ts b/src/apps/agents/utils/getTestProfiles.ts new file mode 100644 index 000000000..f1cfad240 --- /dev/null +++ b/src/apps/agents/utils/getTestProfiles.ts @@ -0,0 +1,15 @@ +import GetProfilesOptions from '../serviceFactory/options/GetProfilesOptions'; +import GetProfilesResult from '../serviceFactory/results/GetProfilesResult'; +import service from './testService'; +import { + TEST_CLIENT, + TEST_MBOX_AGENT, +} from './testValues'; + +export default (optsOverrides: Partial = {}): Promise => { + return service.getProfiles({ + agent: TEST_MBOX_AGENT, + client: TEST_CLIENT, + ...optsOverrides, + }); +}; diff --git a/src/apps/agents/utils/overwriteProfileOutsideClient.ts b/src/apps/agents/utils/overwriteProfileOutsideClient.ts new file mode 100644 index 000000000..dfd5c1cf4 --- /dev/null +++ b/src/apps/agents/utils/overwriteProfileOutsideClient.ts @@ -0,0 +1,6 @@ +import ClientModel from '../models/ClientModel'; +import createTextProfile from './createTextProfile'; + +export default async (client: ClientModel) => { + await createTextProfile({ client }); +}; diff --git a/src/apps/agents/utils/parseJson.ts b/src/apps/agents/utils/parseJson.ts new file mode 100644 index 000000000..240f615b1 --- /dev/null +++ b/src/apps/agents/utils/parseJson.ts @@ -0,0 +1,11 @@ +import JsonSyntaxError from '../errors/JsonSyntaxError'; + +export default (data: string, path: string[]) => { + try { + return JSON.parse(data); + } catch (err) { + if (err instanceof SyntaxError) { + throw new JsonSyntaxError(path); + } + } +}; diff --git a/src/apps/agents/utils/patchProfileOutsideClient.ts b/src/apps/agents/utils/patchProfileOutsideClient.ts new file mode 100644 index 000000000..44b0dc329 --- /dev/null +++ b/src/apps/agents/utils/patchProfileOutsideClient.ts @@ -0,0 +1,19 @@ +import stringToStream from 'string-to-stream'; +import ClientModel from '../models/ClientModel'; +import service from './testService'; +import { + JSON_CONTENT_TYPE, + TEST_MBOX_AGENT, + TEST_OBJECT_CONTENT, + TEST_PROFILE_ID, +} from './testValues'; + +export default async (client: ClientModel) => { + await service.patchProfile({ + agent: TEST_MBOX_AGENT, + client, + content: stringToStream(TEST_OBJECT_CONTENT), + contentType: JSON_CONTENT_TYPE, + profileId: TEST_PROFILE_ID, + }); +}; diff --git a/src/apps/agents/utils/scopes.ts b/src/apps/agents/utils/scopes.ts new file mode 100644 index 000000000..8ed1f9493 --- /dev/null +++ b/src/apps/agents/utils/scopes.ts @@ -0,0 +1,28 @@ +export const ALL = 'all'; +export const ALL_READ = 'all/read'; + +export const XAPI_ALL = 'xapi/all'; +export const XAPI_READ = 'xapi/read'; +export const XAPI_PROFILE_ALL = 'profile'; + +export const PROFILE_READ_SCOPES = [ + ALL, + ALL_READ, + XAPI_ALL, + XAPI_READ, + XAPI_PROFILE_ALL, +]; + +export const PROFILE_WRITE_SCOPES = [ + ALL, + XAPI_ALL, + XAPI_PROFILE_ALL, +]; + +export default [ + ALL, + ALL_READ, + XAPI_ALL, + XAPI_READ, + XAPI_PROFILE_ALL, +]; diff --git a/src/apps/agents/utils/testService.ts b/src/apps/agents/utils/testService.ts new file mode 100644 index 000000000..95d6193fe --- /dev/null +++ b/src/apps/agents/utils/testService.ts @@ -0,0 +1,7 @@ +import * as sourceMapSupport from 'source-map-support'; +sourceMapSupport.install(); + +import serviceFactory from '../serviceFactory'; +import Service from '../serviceFactory/Service'; +const serviceFacade: Service = serviceFactory(); +export default serviceFacade; diff --git a/src/apps/agents/utils/testValues.ts b/src/apps/agents/utils/testValues.ts new file mode 100644 index 000000000..4716e7470 --- /dev/null +++ b/src/apps/agents/utils/testValues.ts @@ -0,0 +1,77 @@ +import Agent from '../models/Agent'; +import ClientModel from '../models/ClientModel'; +import { jsonContentType } from '../utils/constants'; +import { ALL, XAPI_PROFILE_ALL } from './scopes'; + +export const TEST_CLIENT: ClientModel = { + _id: '58fe13e34effd3c26a7fc4b8', + isTrusted: true, + lrs_id: '58fe13e34effd3c26a7fc4b7', + organisation: '58fe13e34effd3c26a7fc4b6', + scopes: [ALL], +}; + +export const TEST_INVALID_SCOPE_TOKEN = 'invalid_scope_client'; +export const TEST_INVALID_SCOPE_CLIENT: ClientModel = { + ...TEST_CLIENT, + scopes: ['invalid_scope'], +}; + +export const TEST_VALID_SCOPE_TOKEN = 'valid_scope_client'; +export const TEST_VALID_SCOPE_CLIENT: ClientModel = { + ...TEST_CLIENT, + scopes: [XAPI_PROFILE_ALL], +}; + +export const TEST_OUTSIDE_STORE_TOKEN = 'outside_store_client'; +export const TEST_CLIENT_OUTSIDE_STORE: ClientModel = { + ...TEST_CLIENT, + lrs_id: '58fe13e34effd3c26a7fc4c7', +}; + +export const TEST_OUTSIDE_ORG_TOKEN = 'outside_org_client'; +export const TEST_CLIENT_OUTSIDE_ORG: ClientModel = { + ...TEST_CLIENT, + organisation: '58fe13e34effd3c26a7fc4c6', +}; + +export const TEST_UNTRUSTED_TOKEN = 'untrusted_client'; +export const TEST_EXPIRED_ORG_TOKEN = 'expired_org_client'; +export const TEST_MISSING_TOKEN = 'Basic missing_token'; + +export const TEST_MBOX_AGENT: Agent = { + mbox: 'mailto:test_agent@example.org', +}; +export const TEST_MBOXSHA1_AGENT: Agent = { + mbox_sha1sum: 'aabbd60ef591bcc908fff6fd2571e8ef8d62461f', +}; +export const TEST_OPENID_AGENT: Agent = { + openid: 'http://www.example.com', +}; +export const TEST_ACCOUNT_AGENT: Agent = { + account: { + homePage: 'http://www.example.org', + name: 'dummy_account_nane', + }, +}; +export const TEST_INVALID_AGENT = { + mbox: 'hello', +}; +export const TEST_IMMUTABLE_AGENT: Agent = { + mbox: 'mailto:immutable@example.org', +}; + +export const TEST_PROFILE_ID = 'dummy_profile_id'; +export const TEST_INVALID_TIMESTAMP = '2'; + +export const TEST_CONTENT = 'dummy_content'; +export const TEST_IMMUTABLE_CONTENT = 'immutable_content'; +export const TEST_JSON_CONTENT = '[]'; +export const TEST_OBJECT_CONTENT = '{"foo":1}'; +export const TEST_OBJECT_PATCH_CONTENT = '{"bar":2}'; +export const TEST_OBJECT_MERGED_CONTENT = '{"foo":1,"bar":2}'; +export const TEST_INVALID_JSON_CONTENT = '{"foo:1,"bar":2}'; + +export const TEXT_CONTENT_TYPE = 'text/plain'; +export const JSON_CONTENT_TYPE = jsonContentType; +export const ALTERNATE_CONTENT_TYPE = 'application/x-www-form-urlencoded'; diff --git a/src/apps/statements/repo/storageRepo/clearRepo/azure.ts b/src/apps/statements/repo/storageRepo/clearRepo/azure.ts index e20d9d70d..4a74fda66 100644 --- a/src/apps/statements/repo/storageRepo/clearRepo/azure.ts +++ b/src/apps/statements/repo/storageRepo/clearRepo/azure.ts @@ -20,6 +20,6 @@ export default (config: FacadeConfig) => { }, ); await Promise.all(deletePromises); - } while (marker !== undefined); + } while (marker !== ''); }; }; diff --git a/src/apps/statements/repo/storageRepo/utils/s3Storage/factory.ts b/src/apps/statements/repo/storageRepo/utils/s3Storage/factory.ts index a2d1070f7..b65900219 100644 --- a/src/apps/statements/repo/storageRepo/utils/s3Storage/factory.ts +++ b/src/apps/statements/repo/storageRepo/utils/s3Storage/factory.ts @@ -14,7 +14,7 @@ export default (factoryConfig: FactoryConfig = {}): Facade => { apiVersion: '2006-03-01', signatureVersion: 'v4', ...factoryConfig.awsConfig, - }), + }) as any, bucketName: defaultTo(factoryConfig.bucketName, 'xapi-server'), subFolder: defaultTo(factoryConfig.subFolder, '/storage'), }; diff --git a/src/apps/states/repo/storage/factory.ts b/src/apps/states/repo/storage/factory.ts index f38dcd9e0..5087140b5 100644 --- a/src/apps/states/repo/storage/factory.ts +++ b/src/apps/states/repo/storage/factory.ts @@ -5,7 +5,7 @@ import { StorageURL, } from '@azure/storage-blob'; import Storage from '@google-cloud/storage'; -import { S3 } from 'aws-sdk'; +import S3 from 'aws-sdk/clients/s3'; import azureStorageRepo from '../../azureStorageRepo'; import googleStorageRepo from '../../googleStorageRepo'; import localStorageRepo from '../../localStorageRepo'; @@ -18,7 +18,7 @@ export default (factoryConfig: FactoryConfig): Repo => { case 's3': return s3StorageRepo({ bucketName: factoryConfig.s3.bucketName, - client: new S3(factoryConfig.s3.awsConfig), + client: new S3(factoryConfig.s3.awsConfig) as any, subFolder: factoryConfig.s3.subFolder, }); case 'google': diff --git a/src/apps/states/repoFactory/index.ts b/src/apps/states/repoFactory/index.ts index 3d56b90c6..04379c534 100644 --- a/src/apps/states/repoFactory/index.ts +++ b/src/apps/states/repoFactory/index.ts @@ -67,7 +67,7 @@ const getStorageRepo = (): StorageRepo => { case 's3': return s3StorageRepo({ bucketName: config.s3StorageRepo.bucketName, - client: new S3(config.s3StorageRepo.awsConfig), + client: new S3(config.s3StorageRepo.awsConfig) as any, subFolder: config.storageSubFolders.state, }); case 'google': diff --git a/yarn.lock b/yarn.lock index facbf512a..84b613ae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,27 +2,23 @@ # yarn lockfile v1 -"@azure/ms-rest-js@^2.0.0": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-2.0.4.tgz#1a1e0e3b2315619d675ebc04c4e8e0d98ce8aa2b" +"@azure/ms-rest-js@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-1.2.3.tgz#0f08d9ce2e4b681b0348cfd774ae5f37371f1ce7" dependencies: - "@types/node-fetch" "^2.3.7" - "@types/tunnel" "0.0.1" - abort-controller "^3.0.0" - form-data "^2.5.0" - node-fetch "^2.6.0" - tough-cookie "^3.0.1" - tslib "^1.10.0" - tunnel "0.0.6" - uuid "^3.3.2" + axios "^0.18.0" + form-data "^2.3.2" + tough-cookie "^2.4.3" + tslib "^1.9.2" + uuid "^3.2.1" xml2js "^0.4.19" -"@azure/storage-blob@^10.3.0": - version "10.4.0" - resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-10.4.0.tgz#b4997de3bf53d9a55d233e0e4f0b6e10347e6124" +"@azure/storage-blob@10.3.0": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-10.3.0.tgz#a93043dce9a2b136b306ef00d1ef14bea49dde73" dependencies: - "@azure/ms-rest-js" "^2.0.0" - events "^3.0.0" + "@azure/ms-rest-js" "1.2.3" + events "3.0.0" tslib "^1.9.3" "@babel/code-frame@^7.0.0": @@ -56,9 +52,9 @@ reflect-metadata "^0.1.12" tslib "^1.8.1" -"@google-cloud/common@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-0.17.0.tgz#8ef558750db481fc10a13757a49479ab9a1c8c07" +"@google-cloud/common@^0.15.1": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-0.15.2.tgz#d8b5ba80f16b60a0ca53fc80ead4ae8b2c2e5b4a" dependencies: array-uniq "^1.0.3" arrify "^1.0.1" @@ -67,9 +63,9 @@ duplexify "^3.5.0" ent "^2.2.0" extend "^3.0.1" - google-auto-auth "^0.10.0" + google-auto-auth "^0.8.0" is "^3.2.0" - log-driver "1.2.7" + log-driver "1.2.5" methmeth "^1.1.0" modelo "^4.2.0" request "^2.79.0" @@ -79,31 +75,29 @@ string-format-obj "^1.1.0" through2 "^2.0.3" -"@google-cloud/storage@^1.5.2": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-1.7.0.tgz#07bff573d92d5c294db6a04af246688875a8f74b" +"@google-cloud/storage@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-1.5.2.tgz#5e206603570f5d1cee4681b23919d8f3655e4e60" dependencies: - "@google-cloud/common" "^0.17.0" + "@google-cloud/common" "^0.15.1" arrify "^1.0.0" async "^2.0.1" - compressible "^2.0.12" concat-stream "^1.5.0" create-error-class "^3.0.2" duplexify "^3.5.0" extend "^3.0.0" - gcs-resumable-upload "^0.10.2" + gcs-resumable-upload "^0.8.2" hash-stream-validation "^0.2.1" is "^3.0.1" - mime "^2.2.0" mime-types "^2.0.8" once "^1.3.1" - pumpify "^1.5.1" - request "^2.85.0" + pumpify "^1.3.3" + request "^2.83.0" safe-buffer "^5.1.1" snakeize "^0.1.0" stream-events "^1.0.1" + string-format-obj "^1.0.0" through2 "^2.0.0" - xdg-basedir "^3.0.0" "@ht2-labs/semantic-release@1.1.90": version "1.1.90" @@ -128,65 +122,7 @@ tslint-immutable "4.9.1" typescript "3.6.2" -"@learninglocker/xapi-activities@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@learninglocker/xapi-activities/-/xapi-activities-4.4.4.tgz#541ad771d4e945fe7181f45d7fc41fcbafecf19b" - dependencies: - "@azure/storage-blob" "^10.3.0" - "@google-cloud/storage" "^1.5.2" - "@learninglocker/xapi-validation" "^2.1.10" - atob "^2.0.3" - aws-sdk "^2.0.1" - bluebird "^3.5.0" - boolean "^0.2.0" - btoa "^1.1.2" - dotenv "^5.0.0" - express "^4.14.1" - fs-extra "^5.0.0" - http-status-codes "^1.3.0" - jscommons "^2.3.0" - lodash "^4.17.4" - mime-types "^2.1.17" - mongodb "^3.0.1" - node-fetch "^2.0.0" - query-string "^5.0.1" - rulr "^4.0.1" - sha1 "^1.1.1" - source-map-support "^0.5.0" - stream-to-string "^1.1.0" - string-to-stream "^1.1.0" - uuid "^3.0.1" - -"@learninglocker/xapi-agents@4.4.3": - version "4.4.3" - resolved "https://registry.yarnpkg.com/@learninglocker/xapi-agents/-/xapi-agents-4.4.3.tgz#f03e2b0abe3b6011dd4ea6876a149846d8ac616d" - dependencies: - "@azure/storage-blob" "^10.3.0" - "@google-cloud/storage" "^1.5.2" - "@learninglocker/xapi-validation" "^2.1.10" - atob "^2.0.3" - aws-sdk "^2.205.0" - bluebird "^3.5.0" - boolean "^0.2.0" - btoa "^1.1.2" - dotenv "^5.0.0" - express "^4.14.1" - fs-extra "^5.0.0" - http-status-codes "^1.3.0" - jscommons "^2.3.0" - lodash "^4.17.4" - mime-types "^2.1.17" - mongodb "^3.0.1" - node-fetch "^2.0.0" - query-string "^5.0.1" - rulr "^4.0.1" - sha1 "^1.1.1" - source-map-support "^0.5.0" - stream-to-string "^1.1.0" - string-to-stream "^1.1.0" - uuid "^3.0.1" - -"@learninglocker/xapi-validation@^2.1.10": +"@learninglocker/xapi-validation@2.1.10": version "2.1.10" resolved "https://registry.yarnpkg.com/@learninglocker/xapi-validation/-/xapi-validation-2.1.10.tgz#ae5c6953b5d4dd2bd4a65005ce46f1e8e8bd2bd2" dependencies: @@ -492,7 +428,7 @@ "@types/bson" "*" "@types/node" "*" -"@types/node-fetch@^2.3.7": +"@types/node-fetch@2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.0.tgz#1c55616a4591bdd15a389fbd0da4a55b9502add5" dependencies: @@ -570,12 +506,6 @@ version "2.3.5" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" -"@types/tunnel@0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c" - dependencies: - "@types/node" "*" - "@types/uuid@3.4.5": version "3.4.5" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.5.tgz#d4dc10785b497a1474eae0ba7f0cb09c0ddfd6eb" @@ -603,12 +533,6 @@ abbrev@1, abbrev@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - dependencies: - event-target-shim "^5.0.0" - accept-language-parser@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791" @@ -831,11 +755,29 @@ atob-lite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" -atob@^2.0.3, atob@^2.1.1: +atob@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" + +atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" -aws-sdk@^2.0.1, aws-sdk@^2.205.0, aws-sdk@^2.249.1, aws-sdk@^2.58.0: +aws-sdk@2.205.0: + version "2.205.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.205.0.tgz#1a93730253e2be027a4bd3af9248cbda0573de80" + dependencies: + buffer "4.9.1" + events "^1.1.1" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.1.0" + xml2js "0.4.17" + xmlbuilder "4.2.1" + +aws-sdk@^2.249.1, aws-sdk@^2.58.0: version "2.517.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.517.0.tgz#415e6732d46c478a4f1867920d25af57651063b1" dependencies: @@ -1383,7 +1325,11 @@ blamer@^0.1.9: bluebird "~2.3.x" xml2js "~0.4.x" -bluebird@^3.0.5, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: +bluebird@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + +bluebird@^3.0.5, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: version "3.5.5" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" @@ -1489,14 +1435,18 @@ btoa-lite@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" -btoa@^1.1.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" +btoa@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.1.2.tgz#3e40b81663f81d2dd6596a4cb714a8dc16cfabe0" buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1863,12 +1813,6 @@ component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" -compressible@^2.0.12: - version "2.0.17" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" - dependencies: - mime-db ">= 1.40.0 < 2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1898,7 +1842,7 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" -configstore@^3.0.0, configstore@^3.1.2: +configstore@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" dependencies: @@ -2454,15 +2398,11 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - -events@1.1.1: +events@1.1.1, events@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" -events@^3.0.0: +events@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -2602,7 +2542,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@^3.0.1, extend@^3.0.2, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.1, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -2851,7 +2791,7 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@^2.3.1: +form-data@^2.3.1, form-data@^2.3.2: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" dependencies: @@ -2911,7 +2851,7 @@ from2@^2.1.0, from2@^2.1.1, from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-extra@^5.0.0: +fs-extra@5.0.0, fs-extra@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" dependencies: @@ -2979,32 +2919,24 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaxios@^1.0.4: - version "1.8.4" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-1.8.4.tgz#e08c34fe93c0a9b67a52b7b9e7a64e6435f9a339" - dependencies: - abort-controller "^3.0.0" - extend "^3.0.2" - https-proxy-agent "^2.2.1" - node-fetch "^2.3.0" - -gcp-metadata@^0.6.1, gcp-metadata@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-0.6.3.tgz#4550c08859c528b370459bd77a7187ea0bdbc4ab" +gcp-metadata@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-0.3.1.tgz#313814456e7c3d0eeb8f8b084b33579e886f829a" dependencies: - axios "^0.18.0" - extend "^3.0.1" - retry-axios "0.3.2" + extend "^3.0.0" + retry-request "^3.0.0" -gcs-resumable-upload@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-0.10.2.tgz#7f29b3ee23dcec4170367c0711418249c660545f" +gcs-resumable-upload@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-0.8.2.tgz#37df02470430395a789a637e72cabc80677ae964" dependencies: - configstore "^3.1.2" - google-auto-auth "^0.10.0" - pumpify "^1.4.0" - request "^2.85.0" - stream-events "^1.0.3" + buffer-equal "^1.0.0" + configstore "^3.0.0" + google-auto-auth "^0.7.1" + pumpify "^1.3.3" + request "^2.81.0" + stream-events "^1.0.1" + through2 "^2.0.0" genfun@^5.0.0: version "5.0.0" @@ -3168,33 +3100,48 @@ globby@^10.0.0: merge2 "^1.2.3" slash "^3.0.0" -google-auth-library@^1.3.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-1.6.1.tgz#9c73d831ad720c0c3048ab89d0ffdec714d07dd2" +google-auth-library@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e" dependencies: - axios "^0.18.0" - gcp-metadata "^0.6.3" - gtoken "^2.3.0" - jws "^3.1.5" + gtoken "^1.2.1" + jws "^3.1.4" + lodash.noop "^3.0.1" + request "^2.74.0" + +google-auth-library@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.12.0.tgz#a3fc6c296d00bb54e4d877ef581a05947330d07f" + dependencies: + gtoken "^1.2.3" + jws "^3.1.4" lodash.isstring "^4.0.1" - lru-cache "^4.1.3" - retry-axios "^0.3.2" + lodash.merge "^4.6.0" + request "^2.81.0" -google-auto-auth@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.10.1.tgz#68834a6f3da59a6cb27fce56f76e3d99ee49d0a2" +google-auto-auth@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.7.2.tgz#bf9352d5c4a0897bf31fd9c491028b765fbea71e" dependencies: async "^2.3.0" - gcp-metadata "^0.6.1" - google-auth-library "^1.3.1" + gcp-metadata "^0.3.0" + google-auth-library "^0.10.0" request "^2.79.0" -google-p12-pem@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-1.0.4.tgz#b77fb833a2eb9f7f3c689e2e54f095276f777605" +google-auto-auth@^0.8.0: + version "0.8.2" + resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.8.2.tgz#928ee8954514a2ea179de8dd4e97f04d40d13d0a" dependencies: - node-forge "^0.8.0" - pify "^4.0.0" + async "^2.3.0" + gcp-metadata "^0.3.0" + google-auth-library "^0.12.0" + request "^2.79.0" + +google-p12-pem@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177" + dependencies: + node-forge "^0.7.1" got@^6.7.1: version "6.7.1" @@ -3242,15 +3189,14 @@ growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" -gtoken@^2.3.0: - version "2.3.3" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-2.3.3.tgz#8a7fe155c5ce0c4b71c886cfb282a9060d94a641" +gtoken@^1.2.1, gtoken@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8" dependencies: - gaxios "^1.0.4" - google-p12-pem "^1.0.0" - jws "^3.1.5" - mime "^2.2.0" - pify "^4.0.0" + google-p12-pem "^0.1.0" + jws "^3.0.0" + mime "^1.4.1" + request "^2.72.0" handlebars@^4.0.3, handlebars@^4.1.2: version "4.1.2" @@ -3465,9 +3411,9 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -http-status-codes@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.3.2.tgz#181dfa4455ef454e5e4d827718fca3936680d10d" +http-status-codes@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.3.0.tgz#9cd0e71391773d0671b489d41cbc5094aa4163b6" https-proxy-agent@^2.1.0, https-proxy-agent@^2.2.1: version "2.2.2" @@ -4048,7 +3994,7 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" -jscommons@^2.3.0, jscommons@^2.3.3: +jscommons@^2.3.3: version "2.4.12" resolved "https://registry.yarnpkg.com/jscommons/-/jscommons-2.4.12.tgz#8a0e3e5eab6388f0f5c93621721ad54c23f80280" dependencies: @@ -4162,7 +4108,7 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jws@^3.1.5, jws@^3.2.2: +jws@^3.0.0, jws@^3.1.4, jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" dependencies: @@ -4507,6 +4453,14 @@ lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" +lodash.merge@^4.6.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + +lodash.noop@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -4535,13 +4489,13 @@ lodash.without@~4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: +lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" -log-driver@1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" +log-driver@1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" log-symbols@2.2.0: version "2.2.0" @@ -4580,7 +4534,7 @@ lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" -lru-cache@^4.0.1, lru-cache@^4.1.3: +lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" dependencies: @@ -4783,11 +4737,21 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -mime-db@1.40.0, "mime-db@>= 1.40.0 < 2": +mime-db@1.40.0: version "1.40.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" -mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + +mime-types@2.1.17: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + dependencies: + mime-db "~1.30.0" + +mime-types@^2.0.8, mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" dependencies: @@ -4797,7 +4761,7 @@ mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" -mime@^2.2.0, mime@^2.4.3: +mime@^2.4.3: version "2.4.4" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" @@ -4919,7 +4883,7 @@ moment@^2.11.2, moment@^2.22.1: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" -mongodb@^3.0.1, mongodb@^3.0.2, mongodb@^3.0.8: +mongodb@^3.0.2, mongodb@^3.0.8: version "3.3.1" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.3.1.tgz#31947e3050872d325415694f89119f37d2107a91" dependencies: @@ -5041,13 +5005,17 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@^2.0.0, node-fetch@^2.3.0, node-fetch@^2.6.0: +node-fetch@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.0.0.tgz#982bba43ecd4f2922a29cc186a6bbb0bb73fcba6" + +node-fetch@^2.3.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" -node-forge@^0.8.0: - version "0.8.5" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee" +node-forge@^0.7.1: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" node-gyp@^5.0.2, node-gyp@^5.0.3: version "5.0.3" @@ -5808,10 +5776,6 @@ pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" -pify@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -5913,10 +5877,14 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" -psl@^1.1.24, psl@^1.1.28: +psl@^1.1.24: version "1.3.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd" +psl@^1.1.28: + version "1.3.1" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.1.tgz#d5aa3873a35ec450bc7db9012ad5a7246f6fc8bd" + pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -5931,7 +5899,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3, pumpify@^1.4.0, pumpify@^1.5.1: +pumpify@^1.3.3: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" dependencies: @@ -6124,7 +6092,7 @@ read@1, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.0, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -6304,7 +6272,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.79.0, request@^2.81.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: +request@^2.72.0, request@^2.74.0, request@^2.79.0, request@^2.81.0, request@^2.83.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" dependencies: @@ -6391,10 +6359,6 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -retry-axios@0.3.2, retry-axios@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.3.2.tgz#5757c80f585b4cc4c4986aa2ffd47a60c6d35e13" - retry-request@^3.0.0: version "3.3.2" resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-3.3.2.tgz#fd8e0079e7b0dfc7056e500b6f089437db0da4df" @@ -6420,6 +6384,10 @@ rimraf@2, rimraf@2.7.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6 dependencies: glob "^7.1.3" +rulr@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/rulr/-/rulr-4.0.1.tgz#650a701175d10ac869faeb4102c638dbc1b56f57" + rulr@^4.0.1, rulr@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/rulr/-/rulr-4.0.2.tgz#4d1b89fe884ac36693ffeaae03b70f75305a89b8" @@ -6570,7 +6538,7 @@ setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" -sha1@^1.1.1: +sha1@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" dependencies: @@ -6867,7 +6835,7 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" -stream-events@^1.0.1, stream-events@^1.0.3: +stream-events@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" dependencies: @@ -6884,7 +6852,7 @@ stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" -stream-to-string@^1.1.0: +stream-to-string@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/stream-to-string/-/stream-to-string-1.2.0.tgz#3ca506a097ecbf78b0e0aee0b6fa5c4565412a15" dependencies: @@ -6898,17 +6866,10 @@ strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" -string-format-obj@^1.1.0: +string-format-obj@^1.0.0, string-format-obj@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/string-format-obj/-/string-format-obj-1.1.1.tgz#c7612ca4e2ad923812a81db192dc291850aa1f65" -string-to-stream@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-1.1.1.tgz#aba78f73e70661b130ee3e1c0192be4fef6cb599" - dependencies: - inherits "^2.0.1" - readable-stream "^2.1.0" - string-to-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-2.0.0.tgz#8a361d3ccc2a9b734b901cc92d2f6d6f71311ba5" @@ -7167,11 +7128,10 @@ toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" +tough-cookie@^2.4.3: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" dependencies: - ip-regex "^2.1.0" psl "^1.1.28" punycode "^2.1.1" @@ -7218,7 +7178,7 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" -tslib@^1.10.0, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.3: +tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.2, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -7270,10 +7230,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -7450,15 +7406,30 @@ util-promisify@^2.1.0: dependencies: object.getownpropertydescriptors "^2.0.3" +util.promisify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" +uuid@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + +uuid@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" -uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: +uuid@^3.2.1, uuid@^3.3.2: version "3.3.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" @@ -7633,13 +7604,38 @@ xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" -xml2js@0.4.19, xml2js@^0.4.19, xml2js@~0.4.x: +xml2js@0.4.17: + version "0.4.17" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + dependencies: + sax ">=0.6.0" + xmlbuilder "^4.1.0" + +xml2js@0.4.19, xml2js@~0.4.x: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" dependencies: sax ">=0.6.0" xmlbuilder "~9.0.1" +xml2js@^0.4.19: + version "0.4.22" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02" + dependencies: + sax ">=0.6.0" + util.promisify "~1.0.0" + xmlbuilder "~11.0.0" + +xmlbuilder@4.2.1, xmlbuilder@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" + dependencies: + lodash "^4.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + xmlbuilder@~9.0.1: version "9.0.7" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" From 76450b796755f60d6e0c9ca175ffe8cd8c1959e5 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 4 Sep 2019 14:49:50 +0100 Subject: [PATCH 2/4] ci(yarn): Attempts to fix conformance tests. --- package-lock.json | 910 +++++++++++++++++++++++----------------------- package.json | 26 +- yarn.lock | 350 +++++++++--------- 3 files changed, 633 insertions(+), 653 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1e5cc43e..455f486f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,9 +22,9 @@ } }, "@azure/storage-blob": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-10.4.0.tgz", - "integrity": "sha512-AxDi1G/FjfG7cOt6F/hOH1qfAB/8WTSn7pKBUkFg4B/vLNuC/Bk1fD6XCCsdrEUWZd8T5kX1prpU27NkCDGl6w==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-10.4.1.tgz", + "integrity": "sha512-c1kCZmqF3opk9KwTkip+ET55hrK1gG5Z1uD4U8ZPNFQnNF7CPNVt1TBU27BI7Hb03gsYl72EenJ2nNHXNTi2RQ==", "requires": { "@azure/ms-rest-js": "^2.0.0", "events": "^3.0.0", @@ -194,162 +194,6 @@ "typescript": "3.6.2" } }, - "@learninglocker/xapi-activities": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@learninglocker/xapi-activities/-/xapi-activities-4.4.4.tgz", - "integrity": "sha512-EjaIMM3JM6gw6zbIWcm2bBLpO7ZkBF1U8XO2N6/SSpfH6fXutmLwSECuGHLS4OkFzCBMMnzDFHeHn89bZLUu8w==", - "requires": { - "@azure/storage-blob": "^10.3.0", - "@google-cloud/storage": "^1.5.2", - "@learninglocker/xapi-validation": "^2.1.10", - "atob": "^2.0.3", - "aws-sdk": "^2.0.1", - "bluebird": "^3.5.0", - "boolean": "^0.2.0", - "btoa": "^1.1.2", - "dotenv": "^5.0.0", - "express": "^4.14.1", - "fs-extra": "^5.0.0", - "http-status-codes": "^1.3.0", - "jscommons": "^2.3.0", - "lodash": "^4.17.4", - "mime-types": "^2.1.17", - "mongodb": "^3.0.1", - "node-fetch": "^2.0.0", - "query-string": "^5.0.1", - "rulr": "^4.0.1", - "sha1": "^1.1.1", - "source-map-support": "^0.5.0", - "stream-to-string": "^1.1.0", - "string-to-stream": "^1.1.0", - "uuid": "^3.0.1" - }, - "dependencies": { - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "string-to-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", - "integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.1.0" - } - } - } - }, - "@learninglocker/xapi-agents": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@learninglocker/xapi-agents/-/xapi-agents-4.4.3.tgz", - "integrity": "sha512-4DGJr9OCPU1Z9CnNqIY2Y0r8K7PJrPDAxS02hW1SAvHFligjAy4WEKgVi8k2l0RVNCYrz+iTlEsnnrAnX8Xslw==", - "requires": { - "@azure/storage-blob": "^10.3.0", - "@google-cloud/storage": "^1.5.2", - "@learninglocker/xapi-validation": "^2.1.10", - "atob": "^2.0.3", - "aws-sdk": "^2.205.0", - "bluebird": "^3.5.0", - "boolean": "^0.2.0", - "btoa": "^1.1.2", - "dotenv": "^5.0.0", - "express": "^4.14.1", - "fs-extra": "^5.0.0", - "http-status-codes": "^1.3.0", - "jscommons": "^2.3.0", - "lodash": "^4.17.4", - "mime-types": "^2.1.17", - "mongodb": "^3.0.1", - "node-fetch": "^2.0.0", - "query-string": "^5.0.1", - "rulr": "^4.0.1", - "sha1": "^1.1.1", - "source-map-support": "^0.5.0", - "stream-to-string": "^1.1.0", - "string-to-stream": "^1.1.0", - "uuid": "^3.0.1" - }, - "dependencies": { - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "string-to-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", - "integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.1.0" - } - } - } - }, - "@learninglocker/xapi-state": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@learninglocker/xapi-state/-/xapi-state-4.4.4.tgz", - "integrity": "sha512-u7byDNF86TRX9ofhdsArSPYYNhGsrQ8K3nhYULbTyzWufuVQNTL74o82cFjUMsSbpYo0Bj4yDqL86XDZxOHFfw==", - "requires": { - "@azure/storage-blob": "^10.3.0", - "@google-cloud/storage": "^1.5.2", - "@learninglocker/xapi-validation": "^2.1.10", - "atob": "^2.0.3", - "aws-sdk": "^2.205.0", - "bluebird": "^3.5.0", - "boolean": "^0.2.0", - "btoa": "^1.1.2", - "dotenv": "^5.0.0", - "express": "^4.14.1", - "fs-extra": "^5.0.0", - "http-status-codes": "^1.3.0", - "jscommons": "^2.3.0", - "lodash": "^4.17.4", - "mime-types": "^2.1.17", - "mongodb": "^3.0.1", - "node-fetch": "^2.0.0", - "query-string": "^5.0.1", - "rulr": "^4.0.1", - "sha1": "^1.1.1", - "source-map-support": "^0.5.0", - "stream-to-string": "^1.1.0", - "string-to-stream": "^1.1.0", - "uuid": "^3.0.1" - }, - "dependencies": { - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "string-to-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", - "integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.1.0" - } - } - } - }, "@learninglocker/xapi-validation": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/@learninglocker/xapi-validation/-/xapi-validation-2.1.10.tgz", @@ -412,15 +256,13 @@ } }, "@octokit/endpoint": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.3.2.tgz", - "integrity": "sha512-gRjteEM9I6f4D8vtwU2iGUTn9RX/AJ0SVXiqBUEuYEWVGGAVjSXdT0oNmghH5lvQNWs8mwt6ZaultuG6yXivNw==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.3.5.tgz", + "integrity": "sha512-f8KqzIrnzPLiezDsZZPB+K8v8YSv6aKFl7eOu59O46lmlW4HagWl1U6NWl6LmT8d1w7NsKBI3paVtzcnRGO1gw==", "dev": true, "requires": { - "deepmerge": "4.0.0", "is-plain-object": "^3.0.0", - "universal-user-agent": "^3.0.0", - "url-template": "^2.0.8" + "universal-user-agent": "^4.0.0" }, "dependencies": { "is-plain-object": { @@ -441,9 +283,9 @@ } }, "@octokit/request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.0.2.tgz", - "integrity": "sha512-z1BQr43g4kOL4ZrIVBMHwi68Yg9VbkRUyuAgqCp1rU3vbYa69+2gIld/+gHclw15bJWQnhqqyEb7h5a5EqgZ0A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.1.0.tgz", + "integrity": "sha512-I15T9PwjFs4tbWyhtFU2Kq7WDPidYMvRB7spmxoQRZfxSmiqullG+Nz+KbSmpkfnlvHwTr1e31R5WReFRKMXjg==", "dev": true, "requires": { "@octokit/endpoint": "^5.1.0", @@ -452,7 +294,7 @@ "is-plain-object": "^3.0.0", "node-fetch": "^2.3.0", "once": "^1.4.0", - "universal-user-agent": "^3.0.0" + "universal-user-agent": "^4.0.0" }, "dependencies": { "is-plain-object": { @@ -483,9 +325,9 @@ } }, "@octokit/rest": { - "version": "16.28.7", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.28.7.tgz", - "integrity": "sha512-cznFSLEhh22XD3XeqJw51OLSfyL2fcFKUO+v2Ep9MTAFfFLS1cK1Zwd1yEgQJmJoDnj4/vv3+fGGZweG+xsbIA==", + "version": "16.28.9", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.28.9.tgz", + "integrity": "sha512-IKGnX+Tvzt7XHhs8f4ajqxyJvYAMNX5nWfoJm4CQj8LZToMiaJgutf5KxxpxoC3y5w7JTJpW5rnWnF4TsIvCLA==", "dev": true, "requires": { "@octokit/request": "^5.0.0", @@ -499,8 +341,7 @@ "lodash.uniq": "^4.5.0", "octokit-pagination-methods": "^1.1.0", "once": "^1.4.0", - "universal-user-agent": "^3.0.0", - "url-template": "^2.0.8" + "universal-user-agent": "^4.0.0" } }, "@semantic-release/commit-analyzer": { @@ -525,12 +366,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -603,12 +438,6 @@ "universalify": "^0.1.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "p-retry": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.1.0.tgz", @@ -716,16 +545,6 @@ "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "registry-auth-token": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.0.0.tgz", @@ -784,27 +603,11 @@ "p-is-promise": "^2.0.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "p-is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } } } }, @@ -875,6 +678,12 @@ "@types/node": "*" } }, + "@types/cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", + "dev": true + }, "@types/dotenv": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-4.0.3.tgz", @@ -1003,8 +812,7 @@ "@types/node": { "version": "9.6.51", "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.51.tgz", - "integrity": "sha512-5lhC7QM2J3b/+epdwaNfRuG2peN4c9EX+mkd27+SqLKhJSdswHTZvc4aZLBZChi+Wo32+E1DeMZs0fSpu/uBXQ==", - "dev": true + "integrity": "sha512-5lhC7QM2J3b/+epdwaNfRuG2peN4c9EX+mkd27+SqLKhJSdswHTZvc4aZLBZChi+Wo32+E1DeMZs0fSpu/uBXQ==" }, "@types/node-fetch": { "version": "2.5.0", @@ -1012,13 +820,6 @@ "integrity": "sha512-TLFRywthBgL68auWj+ziWu+vnmmcHCDFC/sqCOQf1xTz4hRq8cu79z8CtHU9lncExGBsB8fXA4TiLDLt6xvMzw==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "12.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", - "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==" - } } }, "@types/normalize-package-data": { @@ -1089,6 +890,25 @@ "@types/node": "*" } }, + "@types/superagent": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.3.tgz", + "integrity": "sha512-vy2licJQwOXrTAe+yz9SCyUVXAkMgCeDq9VHzS5CWJyDU1g6CI4xKb4d5sCEmyucjw5sG0y4k2/afS0iv/1D0Q==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/supertest": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.8.tgz", + "integrity": "sha512-wcax7/ip4XSSJRLbNzEIUVy2xjcBIZZAuSd2vtltQfRK7kxhx5WMHbLHkYdxN3wuQCrwpYrg86/9byDjPXoGMA==", + "dev": true, + "requires": { + "@types/superagent": "*" + } + }, "@types/tough-cookie": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", @@ -1101,13 +921,6 @@ "integrity": "sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "12.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", - "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==" - } } }, "@types/uuid": { @@ -1163,6 +976,21 @@ "requires": { "mime-types": "~2.1.24", "negotiator": "0.6.2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } } }, "agent-base": { @@ -1354,9 +1182,9 @@ "dev": true }, "aws-sdk": { - "version": "2.517.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.517.0.tgz", - "integrity": "sha512-CX0b+8StmzAGQOP5eoBYJExpjTdPeVPig3NC4crq71/0LkrIDGcc6ekVEl0Rx23WTyGDzExeDD1be1HvQNwHdA==", + "version": "2.522.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.522.0.tgz", + "integrity": "sha512-JNUVaBqXwzDVqR/9dDw4a55aVsdDQYlf/cBM5bSj/g95wbuNWMzrY1TfAxEfSKwH0llp/1/xdXP75AKKp2UoSg==", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -1466,6 +1294,12 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -2071,6 +1905,12 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -2201,9 +2041,9 @@ } }, "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" }, "body-parser": { "version": "1.19.0", @@ -2230,6 +2070,11 @@ "ms": "2.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -2247,6 +2092,11 @@ "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-1.16.0.tgz", "integrity": "sha1-1s4TgIUnr8gLaQkvFWBmVeWyHxo=" }, + "bowser": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.5.3.tgz", + "integrity": "sha512-aWCA+CKfKNL/WGzNgjmK+Whp57JMzboZMwJ5gy2jDj2bEIjbMCb3ImGX+V++5wsJftyFiDIbOjRXl60ycniVqg==" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -2509,12 +2359,6 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -2654,9 +2498,9 @@ "dev": true }, "colorette": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.0.8.tgz", - "integrity": "sha512-X6Ck90ReaF+EfKdVGB7vdIQ3dr651BbIrBwY5YBKg13fjH+940sTtp7/Pkx33C6ntYfQcRumOs/aUQhaRPpbTQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", + "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==" }, "colors": { "version": "1.3.3", @@ -2709,6 +2553,13 @@ "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", "requires": { "mime-db": ">= 1.40.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.41.0.tgz", + "integrity": "sha512-B5gxBI+2K431XW8C2rcc/lhppbuji67nf9v39eH8pkWoZDxnAL0PxdpH32KYRScniF8qDHBDlI+ipgg5WrCUYw==" + } } }, "concat-map": { @@ -2904,6 +2755,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -3022,6 +2879,13 @@ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } } }, "decamelize": { @@ -3068,12 +2932,6 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, - "deepmerge": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.0.0.tgz", - "integrity": "sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww==", - "dev": true - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3252,9 +3110,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.248", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.248.tgz", - "integrity": "sha512-+hQe6xqpODLw9Nr80KoT0/S+YarjNbI9wgZchkOopJLBLPgAsniK184P0IGVs/0NsoZf4lBnQhOsjen9a47Hrg==", + "version": "1.3.252", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.252.tgz", + "integrity": "sha512-NWJ5TztDnjExFISZHFwpoJjMbLUifsNBnx7u2JI0gCw6SbKyQYYWWtBHasO/jPtHym69F4EZuTpRNGN11MT/jg==", "dev": true }, "emoji-regex": { @@ -3314,16 +3172,6 @@ "requires": { "pump": "^3.0.0" } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } } } }, @@ -3337,17 +3185,21 @@ } }, "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.1.tgz", + "integrity": "sha512-cp/Tb1oA/rh2X7vqeSOvM+TSo3UkJLX70eNihgVEvnzwAgikjkTFr/QVgRCaxjm0knCNQzNoxxxcw2zO2LJdZA==", "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", "has": "^1.0.3", + "has-symbols": "^1.0.0", "is-callable": "^1.1.4", "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.0.0", + "string.prototype.trimright": "^2.0.0" } }, "es-to-primitive": { @@ -3470,6 +3322,11 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -3531,6 +3388,11 @@ "ms": "2.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -3673,6 +3535,21 @@ "to-regex-range": "^5.0.1" } }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3778,6 +3655,11 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -3817,6 +3699,21 @@ "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" + }, + "dependencies": { + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + } } }, "fined": { @@ -3881,6 +3778,12 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -3987,9 +3890,9 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getopts": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.4.tgz", - "integrity": "sha512-Rz7DGyomZjrenu9Jx4qmzdlvJgvrEFHXHvjK0FcZtcTC1U5FmES7OdZHUwMuSnEE6QvBvwse1JODKj7TgbSEjQ==" + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", + "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" }, "getpass": { "version": "0.1.7", @@ -4068,6 +3971,23 @@ "dev": true, "requires": { "is-glob": "^4.0.1" + }, + "dependencies": { + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + } } }, "global-dirs": { @@ -4143,6 +4063,22 @@ "lodash.isstring": "^4.0.1", "lru-cache": "^4.1.3", "retry-axios": "^0.3.2" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } } }, "google-auto-auth": { @@ -4222,9 +4158,9 @@ } }, "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.2.0.tgz", + "integrity": "sha512-Kb4xn5Qh1cxAKvQnzNWZ512DhABzyFNmsaJf3OAkWNa4NkaqWcNI8Tao8Tasi0/F4JD9oyG0YxuFyvyR57d+Gw==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -4349,9 +4285,9 @@ "dev": true }, "helmet": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.20.0.tgz", - "integrity": "sha512-Ob+TqmQFZ5f7WgP8kBbAzNPsbf6p1lOj5r+327/ymw/IILWih3wcx9u/u/S8Mwv5wbBkO7Li6x5s23t3COhUKw==", + "version": "3.20.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.20.1.tgz", + "integrity": "sha512-em+X5Wz/f0yqoRsBnpnVy3wJHSiIeskX3FQn30szBh1tILaOeSRRLkShuUVFlk/o4qTYjWxdHg4FrRe45iBWHg==", "requires": { "depd": "2.0.0", "dns-prefetch-control": "0.2.0", @@ -4360,7 +4296,7 @@ "feature-policy": "0.3.0", "frameguard": "3.1.0", "helmet-crossdomain": "0.4.0", - "helmet-csp": "2.8.0", + "helmet-csp": "2.9.0", "hide-powered-by": "1.1.0", "hpkp": "2.0.0", "hsts": "2.2.0", @@ -4383,14 +4319,14 @@ "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" }, "helmet-csp": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.8.0.tgz", - "integrity": "sha512-MlCPeM0Sm3pS9RACRihx70VeTHmkQwa7sum9EK1tfw1VZyvFU0dBWym9nHh3CRkTRNlyNm/WFCMvuh9zXkOjNw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.9.0.tgz", + "integrity": "sha512-DGGOQtOLM7ZQpjbf/uvUonq1yG/rFgsBuK10ZJt2AtxUJxqfkPvfmP9aLUmgH9IactiRiYoiFY72YYSPl1TLTQ==", "requires": { + "bowser": "2.5.3", "camelize": "1.0.0", "content-security-policy-builder": "2.1.0", - "dasherize": "2.0.0", - "platform": "1.3.5" + "dasherize": "2.0.0" } }, "hide-powered-by": { @@ -4667,11 +4603,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -4746,6 +4677,14 @@ "dev": true, "requires": { "ci-info": "^1.5.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + } } }, "is-data-descriptor": { @@ -4805,11 +4744,6 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", @@ -4825,14 +4759,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "requires": { - "is-extglob": "^2.1.1" - } - }, "is-installed-globally": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", @@ -5089,6 +5015,11 @@ "winston-aws-cloudwatch": "^2.0.0" }, "dependencies": { + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, "boolean": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/boolean/-/boolean-0.1.3.tgz", @@ -5213,13 +5144,6 @@ "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^5.6.0" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } } }, "jsprim": { @@ -5267,27 +5191,32 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" }, "knex": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.19.2.tgz", - "integrity": "sha512-TVYvlp2esS4LjjJSz8XuE48bPJq4N3lWnETQVgJ3hXPEqjiDjxcTa3bCn6F5ipQuBaMAAaFHNrqsZm7BttogdA==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.19.3.tgz", + "integrity": "sha512-HN32QB5PVkUYfvE4UoK/Tbf6UQ7CLEgS0PL8EP6xfonsP0IPZr2M84dy1dIy2KnB5dx+XO6NNEPgfzo8Y8BYzA==", "requires": { "bluebird": "^3.5.5", - "colorette": "1.0.8", + "colorette": "1.1.0", "commander": "^2.20.0", "debug": "4.1.1", - "getopts": "2.2.4", + "getopts": "2.2.5", "inherits": "~2.0.4", "interpret": "^1.2.0", "liftoff": "3.1.0", "lodash": "^4.17.15", "mkdirp": "^0.5.1", - "pg-connection-string": "2.0.0", + "pg-connection-string": "2.1.0", "tarn": "^2.0.0", "tildify": "2.0.0", - "uuid": "^3.3.2", + "uuid": "^3.3.3", "v8flags": "^3.1.3" }, "dependencies": { + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -5295,11 +5224,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -5369,6 +5293,12 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, @@ -5528,6 +5458,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -5641,12 +5572,6 @@ "trim-newlines": "^2.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -5738,16 +5663,16 @@ "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "requires": { - "mime-db": "1.40.0" + "mime-db": "~1.30.0" } }, "mimic-fn": { @@ -5772,9 +5697,10 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "minimist-options": { "version": "3.0.2", @@ -5811,6 +5737,13 @@ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } } }, "mocha": { @@ -6007,16 +5940,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -6106,9 +6029,9 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "mongodb": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.1.tgz", - "integrity": "sha512-Q92LLlPUTBj/Z4DyeznwPJjvgOYUcxZsBfek3Ba9mVhzH41n70vSubo+BmemI98kOefHTSncrTyb3OaxdLSgDw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.0.tgz", + "integrity": "sha512-QYa8YEN5uiJyIFdnn1vmBtiSveyygmQghsaL/RDnHqUzjGvkYe0vRg6UikCKba06cg6El/Lu7qzOYnR3vMhwlA==", "requires": { "bson": "^1.1.1", "require_optional": "^1.0.1", @@ -6134,13 +6057,18 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nan": { "version": "2.14.0", @@ -6273,13 +6201,19 @@ "object-assign": "^4.1.0", "strict-uri-encode": "^1.0.0" } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true } } }, "npm": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.11.2.tgz", - "integrity": "sha512-OAkXqI4bm5MUvqVvqe6rxCXmJqrln8VDlkdftpOoayHKazz8IOCJAiCuKmz0TchL224EAKeG86umuD6RYNpuEg==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.11.3.tgz", + "integrity": "sha512-K2h+MPzZiY39Xf6eHEdECe/LKoJXam4UCflz5kIxoskN3LQFeYs5fqBGT5i4TtM/aBk+86Mcf+jgXs/WuWAutQ==", "dev": true, "requires": { "JSONStream": "^1.3.5", @@ -6359,7 +6293,7 @@ "npm-lifecycle": "^3.1.3", "npm-package-arg": "^6.1.1", "npm-packlist": "^1.4.4", - "npm-pick-manifest": "^3.0.0", + "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.2", "npm-registry-fetch": "^4.0.0", "npm-user-validate": "~1.0.0", @@ -6374,7 +6308,7 @@ "query-string": "^6.8.2", "qw": "~1.0.1", "read": "~1.0.7", - "read-cmd-shim": "^1.0.3", + "read-cmd-shim": "^1.0.4", "read-installed": "~4.0.3", "read-package-json": "^2.1.0", "read-package-tree": "^5.3.1", @@ -8463,7 +8397,7 @@ } }, "npm-pick-manifest": { - "version": "3.0.0", + "version": "3.0.2", "bundled": true, "dev": true, "requires": { @@ -8897,7 +8831,7 @@ } }, "read-cmd-shim": { - "version": "1.0.3", + "version": "1.0.4", "bundled": true, "dev": true, "requires": { @@ -12450,6 +12384,12 @@ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==" }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -12566,6 +12506,14 @@ "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } } }, "os-homedir": { @@ -12866,9 +12814,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg-connection-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.0.0.tgz", - "integrity": "sha1-Pu/lmX4G2Ugh5NUC5CtqHHP434I=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.1.0.tgz", + "integrity": "sha512-bhlV7Eq09JrRIvo1eKngpwuqKtJnNhZdpdOlvrPrA4dxqXPjxSrbNrfnIDmTpwMyRszrcV4kU5ZA4mMsQUrjdg==" }, "picomatch": { "version": "2.0.7", @@ -12891,11 +12839,6 @@ "load-json-file": "^4.0.0" } }, - "platform": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -12944,14 +12887,15 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", - "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.1.tgz", + "integrity": "sha512-2KLd5fKOdAfShtY2d/8XDWVRnmp3zp40Qt6ge2zBPFARLXOGUf2fHD5eg+TV/5oxBtQKVhjUaKFsAaE4HnwfSA==" }, "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -12965,6 +12909,17 @@ "duplexify": "^3.6.0", "inherits": "^2.0.3", "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } } }, "punycode": { @@ -12991,13 +12946,6 @@ "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" - }, - "dependencies": { - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" - } } }, "querystring": { @@ -13043,14 +12991,6 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "read-pkg": { @@ -13359,6 +13299,19 @@ "mime-types": "^2.1.12" } }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -13564,12 +13517,6 @@ "color-convert": "^1.9.0" } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -13679,12 +13626,6 @@ "yallist": "^3.0.2" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "p-limit": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", @@ -13709,16 +13650,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -13798,16 +13729,6 @@ "y18n": "^4.0.0", "yargs-parser": "^13.1.1" } - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -14010,12 +13931,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -14068,6 +13983,11 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -14353,9 +14273,9 @@ } }, "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-format-obj": { "version": "1.1.1", @@ -14410,6 +14330,26 @@ } } }, + "string.prototype.trimleft": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.0.0.tgz", + "integrity": "sha1-aLaqjhYsaoDnbjqKDC50cYbicf8=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.0.2" + } + }, + "string.prototype.trimright": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.0.0.tgz", + "integrity": "sha1-q0pW2AKgH75yk+EehPJNyBZGYd0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.0.2" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -14434,12 +14374,6 @@ "ansi-regex": "^2.0.0" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -14469,6 +14403,42 @@ "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + } + } + }, + "supertest": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", + "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -14774,6 +14744,21 @@ "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.24" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } } }, "typedarray": { @@ -14838,12 +14823,12 @@ } }, "universal-user-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-3.0.0.tgz", - "integrity": "sha512-T3siHThqoj5X0benA5H0qcDnrKGXzU8TKoX15x/tQHw1hQBvIEBHjxQ2klizYsqBOO/Q+WuxoQUihadeeqDnoA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.0.tgz", + "integrity": "sha512-eM8knLpev67iBDizr/YtqkJsF3GK8gzDc6st/WKzrTuPtcsOKW/0IdL4cnMBsU69pOx0otavLWBDGTwg+dB0aA==", "dev": true, "requires": { - "os-name": "^3.0.0" + "os-name": "^3.1.0" } }, "universalify": { @@ -15089,16 +15074,6 @@ "requires": { "pump": "^3.0.0" } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } } } }, @@ -15230,7 +15205,8 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true }, "yargs": { "version": "11.1.0", @@ -15250,15 +15226,35 @@ "which-module": "^2.0.0", "y18n": "^3.2.1", "yargs-parser": "^9.0.2" + }, + "dependencies": { + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } } }, "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } } }, "yargs-unparser": { @@ -15394,16 +15390,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", diff --git a/package.json b/package.json index 817ee5901..d58180a82 100644 --- a/package.json +++ b/package.json @@ -38,36 +38,36 @@ "npm": ">3.0.0" }, "dependencies": { - "@azure/storage-blob": "10.3.0", - "@google-cloud/storage": "1.5.2", - "@learninglocker/xapi-validation": "2.1.10", + "@azure/storage-blob": "^10.3.0", + "@google-cloud/storage": "^1.5.2", + "@learninglocker/xapi-validation": "^2.1.10", "accept-language-parser": "^1.5.0", - "atob": "2.0.3", - "aws-sdk": "2.205.0", + "atob": "^2.0.3", + "aws-sdk": "^2.205.0", "bluebird": "3.5.0", "boolean": "^0.2.0", - "btoa": "1.1.2", + "btoa": "^1.1.2", "dotenv": "^5.0.0", "express": "^4.14.1", - "fs-extra": "5.0.0", - "http-status-codes": "1.3.0", + "fs-extra": "^5.0.0", + "http-status-codes": "^1.3.0", "install": "^0.13.0", "ioredis": "^4.14.0", - "jscommons": "^2.3.3", + "jscommons": "^2.3.0", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.4", "mime-types": "2.1.17", "mongodb": "^3.0.2", - "node-fetch": "2.0.0", + "node-fetch": "^2.0.0", "object-hash": "^1.3.1", "query-string": "^6.8.2", "redis": "^2.8.0", - "rulr": "4.0.1", - "sha1": "1.1.1", + "rulr": "^4.0.0", + "sha1": "^1.1.1", "source-map-support": "^0.5.0", "stream-to-string": "^1.2.0", "string-to-stream": "^2.0.0", - "uuid": "3.0.1" + "uuid": "^3.0.1" }, "devDependencies": { "@ht2-labs/semantic-release": "1.1.90", diff --git a/yarn.lock b/yarn.lock index 84b613ae8..3657e489a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,23 +2,27 @@ # yarn lockfile v1 -"@azure/ms-rest-js@1.2.3": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-1.2.3.tgz#0f08d9ce2e4b681b0348cfd774ae5f37371f1ce7" +"@azure/ms-rest-js@^2.0.0": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-2.0.4.tgz#1a1e0e3b2315619d675ebc04c4e8e0d98ce8aa2b" dependencies: - axios "^0.18.0" - form-data "^2.3.2" - tough-cookie "^2.4.3" - tslib "^1.9.2" - uuid "^3.2.1" + "@types/node-fetch" "^2.3.7" + "@types/tunnel" "0.0.1" + abort-controller "^3.0.0" + form-data "^2.5.0" + node-fetch "^2.6.0" + tough-cookie "^3.0.1" + tslib "^1.10.0" + tunnel "0.0.6" + uuid "^3.3.2" xml2js "^0.4.19" -"@azure/storage-blob@10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-10.3.0.tgz#a93043dce9a2b136b306ef00d1ef14bea49dde73" +"@azure/storage-blob@^10.3.0": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-10.4.1.tgz#3979aa6fbc938d4f6d3b60e54f63d85bd7c077b2" dependencies: - "@azure/ms-rest-js" "1.2.3" - events "3.0.0" + "@azure/ms-rest-js" "^2.0.0" + events "^3.0.0" tslib "^1.9.3" "@babel/code-frame@^7.0.0": @@ -52,9 +56,9 @@ reflect-metadata "^0.1.12" tslib "^1.8.1" -"@google-cloud/common@^0.15.1": - version "0.15.2" - resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-0.15.2.tgz#d8b5ba80f16b60a0ca53fc80ead4ae8b2c2e5b4a" +"@google-cloud/common@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-0.17.0.tgz#8ef558750db481fc10a13757a49479ab9a1c8c07" dependencies: array-uniq "^1.0.3" arrify "^1.0.1" @@ -63,9 +67,9 @@ duplexify "^3.5.0" ent "^2.2.0" extend "^3.0.1" - google-auto-auth "^0.8.0" + google-auto-auth "^0.10.0" is "^3.2.0" - log-driver "1.2.5" + log-driver "1.2.7" methmeth "^1.1.0" modelo "^4.2.0" request "^2.79.0" @@ -75,29 +79,31 @@ string-format-obj "^1.1.0" through2 "^2.0.3" -"@google-cloud/storage@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-1.5.2.tgz#5e206603570f5d1cee4681b23919d8f3655e4e60" +"@google-cloud/storage@^1.5.2": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-1.7.0.tgz#07bff573d92d5c294db6a04af246688875a8f74b" dependencies: - "@google-cloud/common" "^0.15.1" + "@google-cloud/common" "^0.17.0" arrify "^1.0.0" async "^2.0.1" + compressible "^2.0.12" concat-stream "^1.5.0" create-error-class "^3.0.2" duplexify "^3.5.0" extend "^3.0.0" - gcs-resumable-upload "^0.8.2" + gcs-resumable-upload "^0.10.2" hash-stream-validation "^0.2.1" is "^3.0.1" + mime "^2.2.0" mime-types "^2.0.8" once "^1.3.1" - pumpify "^1.3.3" - request "^2.83.0" + pumpify "^1.5.1" + request "^2.85.0" safe-buffer "^5.1.1" snakeize "^0.1.0" stream-events "^1.0.1" - string-format-obj "^1.0.0" through2 "^2.0.0" + xdg-basedir "^3.0.0" "@ht2-labs/semantic-release@1.1.90": version "1.1.90" @@ -122,7 +128,7 @@ tslint-immutable "4.9.1" typescript "3.6.2" -"@learninglocker/xapi-validation@2.1.10": +"@learninglocker/xapi-validation@^2.1.10": version "2.1.10" resolved "https://registry.yarnpkg.com/@learninglocker/xapi-validation/-/xapi-validation-2.1.10.tgz#ae5c6953b5d4dd2bd4a65005ce46f1e8e8bd2bd2" dependencies: @@ -428,7 +434,7 @@ "@types/bson" "*" "@types/node" "*" -"@types/node-fetch@2.5.0": +"@types/node-fetch@2.5.0", "@types/node-fetch@^2.3.7": version "2.5.0" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.0.tgz#1c55616a4591bdd15a389fbd0da4a55b9502add5" dependencies: @@ -506,6 +512,12 @@ version "2.3.5" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" +"@types/tunnel@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c" + dependencies: + "@types/node" "*" + "@types/uuid@3.4.5": version "3.4.5" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.5.tgz#d4dc10785b497a1474eae0ba7f0cb09c0ddfd6eb" @@ -533,6 +545,12 @@ abbrev@1, abbrev@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + dependencies: + event-target-shim "^5.0.0" + accept-language-parser@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791" @@ -755,27 +773,23 @@ atob-lite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" -atob@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" - -atob@^2.1.1: +atob@^2.0.3, atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" -aws-sdk@2.205.0: - version "2.205.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.205.0.tgz#1a93730253e2be027a4bd3af9248cbda0573de80" +aws-sdk@^2.205.0: + version "2.522.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.522.0.tgz#f3419800fad29c03c9344d433c0782c4ad7cf0bb" dependencies: buffer "4.9.1" - events "^1.1.1" + events "1.1.1" + ieee754 "1.1.8" jmespath "0.15.0" querystring "0.2.0" sax "1.2.1" url "0.10.3" - uuid "3.1.0" - xml2js "0.4.17" - xmlbuilder "4.2.1" + uuid "3.3.2" + xml2js "0.4.19" aws-sdk@^2.249.1, aws-sdk@^2.58.0: version "2.517.0" @@ -1435,18 +1449,14 @@ btoa-lite@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" -btoa@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.1.2.tgz#3e40b81663f81d2dd6596a4cb714a8dc16cfabe0" +btoa@^1.1.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" -buffer-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" - buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1813,6 +1823,12 @@ component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" +compressible@^2.0.12: + version "2.0.17" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" + dependencies: + mime-db ">= 1.40.0 < 2" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1842,7 +1858,7 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" -configstore@^3.0.0: +configstore@^3.0.0, configstore@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" dependencies: @@ -2398,11 +2414,15 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" -events@1.1.1, events@^1.1.1: +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + +events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" -events@3.0.0: +events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -2542,7 +2562,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@^3.0.1, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.1, extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -2630,9 +2650,9 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-stream-rotator@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/file-stream-rotator/-/file-stream-rotator-0.4.1.tgz#09f67b86d6ea589d20b7852c51c59de55d916d6d" +file-stream-rotator@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/file-stream-rotator/-/file-stream-rotator-0.5.0.tgz#a50959f8c9cb22b8f634ca75d907ba355c1bcb46" dependencies: moment "^2.11.2" @@ -2791,7 +2811,7 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@^2.3.1, form-data@^2.3.2: +form-data@^2.3.1: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" dependencies: @@ -2851,7 +2871,7 @@ from2@^2.1.0, from2@^2.1.1, from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-extra@5.0.0, fs-extra@^5.0.0: +fs-extra@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" dependencies: @@ -2919,24 +2939,32 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gcp-metadata@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-0.3.1.tgz#313814456e7c3d0eeb8f8b084b33579e886f829a" +gaxios@^1.0.4: + version "1.8.4" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-1.8.4.tgz#e08c34fe93c0a9b67a52b7b9e7a64e6435f9a339" dependencies: - extend "^3.0.0" - retry-request "^3.0.0" + abort-controller "^3.0.0" + extend "^3.0.2" + https-proxy-agent "^2.2.1" + node-fetch "^2.3.0" -gcs-resumable-upload@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-0.8.2.tgz#37df02470430395a789a637e72cabc80677ae964" +gcp-metadata@^0.6.1, gcp-metadata@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-0.6.3.tgz#4550c08859c528b370459bd77a7187ea0bdbc4ab" dependencies: - buffer-equal "^1.0.0" - configstore "^3.0.0" - google-auto-auth "^0.7.1" - pumpify "^1.3.3" - request "^2.81.0" - stream-events "^1.0.1" - through2 "^2.0.0" + axios "^0.18.0" + extend "^3.0.1" + retry-axios "0.3.2" + +gcs-resumable-upload@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-0.10.2.tgz#7f29b3ee23dcec4170367c0711418249c660545f" + dependencies: + configstore "^3.1.2" + google-auto-auth "^0.10.0" + pumpify "^1.4.0" + request "^2.85.0" + stream-events "^1.0.3" genfun@^5.0.0: version "5.0.0" @@ -3100,48 +3128,33 @@ globby@^10.0.0: merge2 "^1.2.3" slash "^3.0.0" -google-auth-library@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e" - dependencies: - gtoken "^1.2.1" - jws "^3.1.4" - lodash.noop "^3.0.1" - request "^2.74.0" - -google-auth-library@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.12.0.tgz#a3fc6c296d00bb54e4d877ef581a05947330d07f" +google-auth-library@^1.3.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-1.6.1.tgz#9c73d831ad720c0c3048ab89d0ffdec714d07dd2" dependencies: - gtoken "^1.2.3" - jws "^3.1.4" + axios "^0.18.0" + gcp-metadata "^0.6.3" + gtoken "^2.3.0" + jws "^3.1.5" lodash.isstring "^4.0.1" - lodash.merge "^4.6.0" - request "^2.81.0" - -google-auto-auth@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.7.2.tgz#bf9352d5c4a0897bf31fd9c491028b765fbea71e" - dependencies: - async "^2.3.0" - gcp-metadata "^0.3.0" - google-auth-library "^0.10.0" - request "^2.79.0" + lru-cache "^4.1.3" + retry-axios "^0.3.2" -google-auto-auth@^0.8.0: - version "0.8.2" - resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.8.2.tgz#928ee8954514a2ea179de8dd4e97f04d40d13d0a" +google-auto-auth@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.10.1.tgz#68834a6f3da59a6cb27fce56f76e3d99ee49d0a2" dependencies: async "^2.3.0" - gcp-metadata "^0.3.0" - google-auth-library "^0.12.0" + gcp-metadata "^0.6.1" + google-auth-library "^1.3.1" request "^2.79.0" -google-p12-pem@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177" +google-p12-pem@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-1.0.4.tgz#b77fb833a2eb9f7f3c689e2e54f095276f777605" dependencies: - node-forge "^0.7.1" + node-forge "^0.8.0" + pify "^4.0.0" got@^6.7.1: version "6.7.1" @@ -3189,14 +3202,15 @@ growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" -gtoken@^1.2.1, gtoken@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8" +gtoken@^2.3.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-2.3.3.tgz#8a7fe155c5ce0c4b71c886cfb282a9060d94a641" dependencies: - google-p12-pem "^0.1.0" - jws "^3.0.0" - mime "^1.4.1" - request "^2.72.0" + gaxios "^1.0.4" + google-p12-pem "^1.0.0" + jws "^3.1.5" + mime "^2.2.0" + pify "^4.0.0" handlebars@^4.0.3, handlebars@^4.1.2: version "4.1.2" @@ -3411,9 +3425,9 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -http-status-codes@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.3.0.tgz#9cd0e71391773d0671b489d41cbc5094aa4163b6" +http-status-codes@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.3.2.tgz#181dfa4455ef454e5e4d827718fca3936680d10d" https-proxy-agent@^2.1.0, https-proxy-agent@^2.2.1: version "2.2.2" @@ -3994,9 +4008,9 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" -jscommons@^2.3.3: - version "2.4.12" - resolved "https://registry.yarnpkg.com/jscommons/-/jscommons-2.4.12.tgz#8a0e3e5eab6388f0f5c93621721ad54c23f80280" +jscommons@^2.3.0: + version "2.4.13" + resolved "https://registry.yarnpkg.com/jscommons/-/jscommons-2.4.13.tgz#4dc5a99a58fb0e5d464cba3fa648aae3d8a9cfbb" dependencies: aws-sdk "^2.249.1" bluebird "^3.5.1" @@ -4004,7 +4018,7 @@ jscommons@^2.3.3: boolean "^0.1.3" cors "^2.8.4" express "^4.16.3" - file-stream-rotator "^0.4.0" + file-stream-rotator "^0.5.0" fs-extra "^5.0.0" helmet "^3.12.1" knex "^0.19.0" @@ -4108,7 +4122,7 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jws@^3.0.0, jws@^3.1.4, jws@^3.2.2: +jws@^3.1.5, jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" dependencies: @@ -4453,14 +4467,6 @@ lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" -lodash.merge@^4.6.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - -lodash.noop@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" - lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -4489,13 +4495,13 @@ lodash.without@~4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" -log-driver@1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" +log-driver@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" log-symbols@2.2.0: version "2.2.0" @@ -4534,7 +4540,7 @@ lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" -lru-cache@^4.0.1: +lru-cache@^4.0.1, lru-cache@^4.1.3: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" dependencies: @@ -4741,6 +4747,10 @@ mime-db@1.40.0: version "1.40.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" +"mime-db@>= 1.40.0 < 2": + version "1.41.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.41.0.tgz#9110408e1f6aa1b34aef51f2c9df3caddf46b6a0" + mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" @@ -4761,7 +4771,7 @@ mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" -mime@^2.4.3: +mime@^2.2.0, mime@^2.4.3: version "2.4.4" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" @@ -5005,17 +5015,13 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.0.0.tgz#982bba43ecd4f2922a29cc186a6bbb0bb73fcba6" - -node-fetch@^2.3.0: +node-fetch@^2.0.0, node-fetch@^2.3.0, node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" -node-forge@^0.7.1: - version "0.7.6" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" +node-forge@^0.8.0: + version "0.8.5" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee" node-gyp@^5.0.2, node-gyp@^5.0.3: version "5.0.3" @@ -5776,6 +5782,10 @@ pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" +pify@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -5899,7 +5909,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: +pumpify@^1.3.3, pumpify@^1.4.0, pumpify@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" dependencies: @@ -6272,7 +6282,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.72.0, request@^2.74.0, request@^2.79.0, request@^2.81.0, request@^2.83.0, request@^2.87.0, request@^2.88.0: +request@^2.79.0, request@^2.81.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" dependencies: @@ -6359,6 +6369,10 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" +retry-axios@0.3.2, retry-axios@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.3.2.tgz#5757c80f585b4cc4c4986aa2ffd47a60c6d35e13" + retry-request@^3.0.0: version "3.3.2" resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-3.3.2.tgz#fd8e0079e7b0dfc7056e500b6f089437db0da4df" @@ -6384,11 +6398,7 @@ rimraf@2, rimraf@2.7.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6 dependencies: glob "^7.1.3" -rulr@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/rulr/-/rulr-4.0.1.tgz#650a701175d10ac869faeb4102c638dbc1b56f57" - -rulr@^4.0.1, rulr@^4.0.2: +rulr@^4.0.0, rulr@^4.0.1, rulr@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/rulr/-/rulr-4.0.2.tgz#4d1b89fe884ac36693ffeaae03b70f75305a89b8" @@ -6538,7 +6548,7 @@ setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" -sha1@1.1.1: +sha1@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" dependencies: @@ -6835,7 +6845,7 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" -stream-events@^1.0.1: +stream-events@^1.0.1, stream-events@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" dependencies: @@ -6866,7 +6876,7 @@ strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" -string-format-obj@^1.0.0, string-format-obj@^1.1.0: +string-format-obj@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/string-format-obj/-/string-format-obj-1.1.1.tgz#c7612ca4e2ad923812a81db192dc291850aa1f65" @@ -7128,10 +7138,11 @@ toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" -tough-cookie@^2.4.3: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" dependencies: + ip-regex "^2.1.0" psl "^1.1.28" punycode "^2.1.1" @@ -7178,7 +7189,7 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" -tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.2, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -7230,6 +7241,10 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -7417,19 +7432,11 @@ utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" -uuid@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" - -uuid@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" -uuid@^3.2.1, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: version "3.3.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" @@ -7604,13 +7611,6 @@ xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" -xml2js@0.4.17: - version "0.4.17" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" - dependencies: - sax ">=0.6.0" - xmlbuilder "^4.1.0" - xml2js@0.4.19, xml2js@~0.4.x: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" @@ -7626,12 +7626,6 @@ xml2js@^0.4.19: util.promisify "~1.0.0" xmlbuilder "~11.0.0" -xmlbuilder@4.2.1, xmlbuilder@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" - dependencies: - lodash "^4.0.0" - xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" From d21eb7ababb21913a344b7894a179dd35768ff5e Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 4 Sep 2019 15:33:45 +0100 Subject: [PATCH 3/4] ci(lgtm): Removes unused variable. --- src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts b/src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts index 5d380b5d5..880d8abed 100644 --- a/src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts +++ b/src/apps/agents/mongoModelsRepo/utils/getProfileFilter.ts @@ -1,4 +1,3 @@ -import { ObjectID } from 'mongodb'; /* tslint:disable-line:no-unused */ import Agent from '../../models/Agent'; import ClientModel from '../../models/ClientModel'; import getProfilesFilter from './getProfilesFilter'; From 2150148063014a7a25e21472786a730883d732d6 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 4 Sep 2019 15:34:18 +0100 Subject: [PATCH 4/4] ci(mocha): Removes Morgan to hopefully fix issue with test logs. --- src/apps/activities/expressPresenter/index.ts | 2 -- src/apps/agents/expressPresenter/index.ts | 2 -- src/apps/statements/expressPresenter/index.ts | 4 ---- src/apps/states/expressPresenter/index.ts | 2 -- 4 files changed, 10 deletions(-) diff --git a/src/apps/activities/expressPresenter/index.ts b/src/apps/activities/expressPresenter/index.ts index 244c7563c..9ef35d28e 100644 --- a/src/apps/activities/expressPresenter/index.ts +++ b/src/apps/activities/expressPresenter/index.ts @@ -1,7 +1,6 @@ import { Router } from 'express'; import mixinCors from 'jscommons/dist/expressPresenter/mixins/cors'; import mixinHelmet from 'jscommons/dist/expressPresenter/mixins/helmet'; -import mixinMorgan from 'jscommons/dist/expressPresenter/mixins/morgan'; import Config from './Config'; import deleteProfile from './deleteProfile'; import getProfiles from './getProfiles'; @@ -13,7 +12,6 @@ export default (config: Config): Router => { router.use(mixinCors()); router.use(mixinHelmet()); - router.use(mixinMorgan(config.morganDirectory)); router.delete('', deleteProfile(config)); router.get('', getProfiles(config)); diff --git a/src/apps/agents/expressPresenter/index.ts b/src/apps/agents/expressPresenter/index.ts index 98ecaa79e..a803118f8 100644 --- a/src/apps/agents/expressPresenter/index.ts +++ b/src/apps/agents/expressPresenter/index.ts @@ -1,7 +1,6 @@ import { Router } from 'express'; import mixinCors from 'jscommons/dist/expressPresenter/mixins/cors'; import mixinHelmet from 'jscommons/dist/expressPresenter/mixins/helmet'; -import mixinMorgan from 'jscommons/dist/expressPresenter/mixins/morgan'; import Config from './Config'; import deleteProfile from './deleteProfile'; import getFullAgent from './getFullAgent'; @@ -14,7 +13,6 @@ export default (config: Config): Router => { router.use(mixinCors()); router.use(mixinHelmet()); - router.use(mixinMorgan(config.morganDirectory)); router.delete('/profile', deleteProfile(config)); router.get('/profile', getProfiles(config)); diff --git a/src/apps/statements/expressPresenter/index.ts b/src/apps/statements/expressPresenter/index.ts index 82d82a788..2af0a743b 100644 --- a/src/apps/statements/expressPresenter/index.ts +++ b/src/apps/statements/expressPresenter/index.ts @@ -1,7 +1,6 @@ import { Router } from 'express'; import mixinCors from 'jscommons/dist/expressPresenter/mixins/cors'; import mixinHelmet from 'jscommons/dist/expressPresenter/mixins/helmet'; -import mixinMorgan from 'jscommons/dist/expressPresenter/mixins/morgan'; import Config from './Config'; import getAbout from './getAbout'; import getFullActivity from './getFullActivity'; @@ -19,19 +18,16 @@ export default (config: Config): Result => { const aboutRouter = Router(); aboutRouter.use(mixinCors()); aboutRouter.use(mixinHelmet()); - aboutRouter.use(mixinMorgan(config.morganDirectory)); aboutRouter.get('', getAbout(config)); const fullActivitiesRouter = Router(); fullActivitiesRouter.use(mixinCors()); fullActivitiesRouter.use(mixinHelmet()); - fullActivitiesRouter.use(mixinMorgan(config.morganDirectory)); fullActivitiesRouter.get('', getFullActivity(config)); const statementsRouter = Router(); statementsRouter.use(mixinCors()); statementsRouter.use(mixinHelmet()); - statementsRouter.use(mixinMorgan(config.morganDirectory)); statementsRouter.get('', getStatements(config)); statementsRouter.put('', putStatement(config)); statementsRouter.post('', postStatements(config)); diff --git a/src/apps/states/expressPresenter/index.ts b/src/apps/states/expressPresenter/index.ts index 709910689..a8b4ba3bb 100644 --- a/src/apps/states/expressPresenter/index.ts +++ b/src/apps/states/expressPresenter/index.ts @@ -1,7 +1,6 @@ import { Router } from 'express'; import mixinCors from 'jscommons/dist/expressPresenter/mixins/cors'; import mixinHelmet from 'jscommons/dist/expressPresenter/mixins/helmet'; -import mixinMorgan from 'jscommons/dist/expressPresenter/mixins/morgan'; import Config from './Config'; import deleteState from './deleteState'; import getStates from './getStates'; @@ -13,7 +12,6 @@ export default (config: Config): Router => { router.use(mixinCors()); router.use(mixinHelmet()); - router.use(mixinMorgan(config.morganDirectory)); router.delete('', deleteState(config)); router.get('', getStates(config));