generated from 8iq/nodejs-hackathon-boilerplate-starter-kit
-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(condo) DOMA-7397 generate initial query files
fix(condo) DOMA-7397 finish GetNewsSharingRecipientsService code fix(condo) DOMA-7397 learn how to launch query fix(condo) DOMA-7397 add example api fix(condo) DOMA-7397 add TESTS_FAKE_CLIENT_EXPRESS_PORT variable fix(condo) DOMA-7397 add new error fix(condo) DOMA-7397 fix more tests fix(condo) DOMA-7397 figure out translations fix(condo) DOMA-7397 figure out graphql types fix(condo) DOMA-7397 figure out tests fix(condo) DOMA-7397 maketypes
- Loading branch information
1 parent
e284780
commit a19b2f5
Showing
14 changed files
with
540 additions
and
1 deletion.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
apps/condo/domains/news/access/GetNewsSharingRecipientsService.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* Generated by `createservice news.GetNewsItemSharingRecipientsService --type queries` | ||
*/ | ||
const get = require('lodash/get') | ||
|
||
const { throwAuthenticationError } = require('@open-condo/keystone/apolloErrorFormatter') | ||
const { getById } = require('@open-condo/keystone/schema') | ||
|
||
const { checkPermissionInUserOrganizationOrRelatedOrganization } = require('@condo/domains/organization/utils/accessSchema') | ||
|
||
async function canGetNewsSharingRecipients ({ authentication: { item: user }, args: { data: { b2bAppContext: { id: b2bAppContextId } } } }) { | ||
if (!user) return throwAuthenticationError() | ||
if (user.deletedAt) return false | ||
if (user.isAdmin || user.isSupport) return true | ||
|
||
const b2bContext = await getById('B2BAppContext', b2bAppContextId) | ||
if (!b2bContext) { return false } | ||
|
||
const organizationId = get(b2bContext, 'organization', null) | ||
if (!organizationId) { return false } | ||
|
||
return await checkPermissionInUserOrganizationOrRelatedOrganization(user.id, organizationId, 'canReadNewsItems') | ||
} | ||
|
||
/* | ||
Rules are logical functions that used for list access, and may return a boolean (meaning | ||
all or no items are available) or a set of filters that limit the available items. | ||
*/ | ||
module.exports = { | ||
canGetNewsSharingRecipients, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
apps/condo/domains/news/schema/GetNewsSharingRecipientsService.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/** | ||
* Generated by `createservice news.GetNewsItemSharingRecipientsService --type queries` | ||
*/ | ||
|
||
/** | ||
* This service is part of "NewsSharing" functionality. | ||
* | ||
* -- | ||
* | ||
* To make your miniapp able to show up on /news page you need to implement getRecipients method. | ||
* | ||
* GetRecipients should return a list of available recipients with relevant data: | ||
* | ||
* Request: | ||
* | ||
* GET <URL>?organizationId=string | ||
* | ||
* Where: | ||
* - URL = getRecipeints endpoint url provided in NewsItemSharingConfig | ||
* | ||
* Response payload: | ||
* [{ | ||
* id: <string> | ||
* subscribers: <number> | ||
* name: <string> | ||
* }, | ||
* {...} | ||
* ] | ||
* | ||
* GetRecipients endpoint should be covered behind BASIC auth | ||
* | ||
* -- | ||
* | ||
* This method is just a proxy between Condo and miniapp. | ||
** | ||
* <Condo /news> -> <Condo.GetNewsSharingRecipientsService> -> BASIC-AUTH -> <Miniapp.GetRecipients> | ||
*/ | ||
|
||
const fetch = require('node-fetch') | ||
|
||
const { GQLError, GQLErrorCode: { BAD_USER_INPUT, INTERNAL_ERROR } } = require('@open-condo/keystone/errors') | ||
const { GQLCustomSchema, getById } = require('@open-condo/keystone/schema') | ||
|
||
const { WRONG_VALUE, NETWORK_ERROR } = require('@condo/domains/common/constants/errors') | ||
const access = require('@condo/domains/news/access/GetNewsSharingRecipientsService') | ||
|
||
/** | ||
* List of possible errors, that this custom schema can throw | ||
* They will be rendered in documentation section in GraphiQL for this custom schema | ||
*/ | ||
const ERRORS = { | ||
NOT_NEWS_SHARING_APP: { | ||
query: 'getNewsItemSharingRecipients', | ||
variable: ['data', 'b2bAppContext'], | ||
code: BAD_USER_INPUT, | ||
type: WRONG_VALUE, | ||
message: 'Provided b2bApp does not support NewsItemSharing', | ||
messageForUser: 'api.newsItem.getNewsSharingRecipients.NOT_NEWS_SHARING_APP', | ||
}, | ||
NEWS_SHARING_APP_REQUEST_FAILED: { | ||
query: 'getNewsItemSharingRecipients', | ||
variable: ['data'], | ||
code: INTERNAL_ERROR, | ||
type: NETWORK_ERROR, | ||
message: 'Could not get a successful response from NewsSharing miniapp', | ||
messageForUser: 'api.newsItem.getNewsSharingRecipients.NEWS_SHARING_APP_REQUEST_FAILED', | ||
}, | ||
NEWS_SHARING_APP_REQUEST_BAD_RESPONSE: { | ||
query: 'getNewsItemSharingRecipients', | ||
variable: ['data'], | ||
code: INTERNAL_ERROR, | ||
type: WRONG_VALUE, | ||
message: 'Response from NewsSharing miniapp was successful, but the data format was incorrect', | ||
messageForUser: 'api.newsItem.getNewsSharingRecipients.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE', | ||
}, | ||
} | ||
|
||
const GetNewsSharingRecipientsService = new GQLCustomSchema('GetNewsSharingRecipientsService', { | ||
types: [ | ||
{ | ||
access: true, | ||
type: 'input GetNewsSharingRecipientsInput { dv: Int!, sender: JSON!, b2bAppContext: B2BAppContextWhereUniqueInput! }', | ||
}, | ||
{ | ||
access: true, | ||
type: 'type GetNewsSharingRecipientsOutput { id: String!, name: String!, recipients: Int }', | ||
}, | ||
], | ||
|
||
queries: [ | ||
{ | ||
access: access.canGetNewsSharingRecipients, | ||
schema: 'getNewsSharingRecipients (data: GetNewsSharingRecipientsInput!): [GetNewsSharingRecipientsOutput]', | ||
resolver: async (parent, args, context, info, extra = {}) => { | ||
const { data } = args | ||
|
||
const { dv, sender, b2bAppContext } = data | ||
|
||
const b2bAppContextData = await getById('B2BAppContext', b2bAppContext.id) | ||
const b2bApp = await getById('B2BApp', b2bAppContextData.app) | ||
|
||
if (!b2bApp.newsSharingConfig) { | ||
throw new GQLError(ERRORS.NOT_NEWS_SHARING_APP) | ||
} | ||
|
||
const newsSharingConfig = await getById('B2BAppNewsSharingConfig', b2bApp.newsSharingConfig) | ||
|
||
if (!newsSharingConfig) { | ||
throw new GQLError(ERRORS.NOT_NEWS_SHARING_APP) | ||
} | ||
|
||
const getRecipientsUrl = newsSharingConfig.getRecipientsUrl | ||
const organizationId = b2bAppContextData.organization | ||
|
||
let getRecipientsResult | ||
|
||
// Check that we can obtain result data | ||
try { | ||
getRecipientsResult = await fetch( | ||
`${getRecipientsUrl}?organizationId=${organizationId}`, | ||
) | ||
} | ||
catch (err) { | ||
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_FAILED) | ||
} | ||
|
||
// If status code of response is not 200, we need to raise an error | ||
if (getRecipientsResult.status !== 200) { | ||
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_FAILED) | ||
} | ||
|
||
// Check that result data is in good shape | ||
const getRecipientsResultData = await getRecipientsResult.json() | ||
|
||
if (!getRecipientsResultData || !Array.isArray(getRecipientsResultData)) { | ||
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE) | ||
} | ||
|
||
getRecipientsResultData.forEach(x => { | ||
if ( | ||
(typeof x.id !== 'string') || | ||
(typeof x.name !== 'string') || | ||
(x.recipients && typeof x.recipients !== 'number') | ||
) { | ||
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE) | ||
} | ||
}) | ||
|
||
return getRecipientsResultData | ||
}, | ||
}, | ||
], | ||
|
||
}) | ||
|
||
module.exports = { | ||
GetNewsSharingRecipientsService, | ||
ERRORS, | ||
} |
170 changes: 170 additions & 0 deletions
170
apps/condo/domains/news/schema/GetNewsSharingRecipientsService.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/** | ||
* Generated by `createservice news.GetNewsItemSharingRecipientsService --type queries` | ||
*/ | ||
|
||
const conf = require('@open-condo/config') | ||
const { makeLoggedInAdminClient, makeClient, expectToThrowAuthenticationError, expectToThrowAccessDeniedErrorToResult, expectToThrowGQLError } = require('@open-condo/keystone/test.utils') | ||
const { expectToThrowAccessDeniedErrorToObj, expectToThrowAuthenticationErrorToObjects } = require('@open-condo/keystone/test.utils') | ||
|
||
const { | ||
createTestB2BAppNewsSharingConfig, | ||
createTestB2BApp, | ||
createTestB2BAppContext, | ||
} = require('@condo/domains/miniapp/utils/testSchema') | ||
const { ERRORS} = require('@condo/domains/news/schema/GetNewsSharingRecipientsService') | ||
const { getNewsSharingRecipientsByTestClient } = require('@condo/domains/news/utils/testSchema') | ||
const { createTestOrganization } = require('@condo/domains/organization/utils/testSchema') | ||
|
||
|
||
const { | ||
createTestOrganizationEmployeeRole, | ||
createTestOrganizationEmployee, | ||
} = require('../../organization/utils/testSchema') | ||
const { makeClientWithNewRegisteredAndLoggedInUser } = require('../../user/utils/testSchema') | ||
const { | ||
SUCCESS_GET_RECIPIENTS_URL, | ||
FAULTY_GET_RECIPIENTS_URL_404, | ||
FAULTY_GET_RECIPIENTS_URL_500, | ||
INCORRECT_GET_RECIPIENTS_URL, | ||
SUCCESS_PREVIEW_URL, | ||
SUCCESS_PUBLISH_URL, | ||
SUCCESS_GET_RECIPIENTS_RESULT, | ||
} = require('../utils/testSchema/NewsSharingTestingApp') | ||
|
||
let adminClient, dummyO10n, staffClientWithPermissions, dummyB2BContextWithNewsSharingConfig | ||
|
||
describe('GetNewsSharingRecipientsService', () => { | ||
beforeAll(async () => { | ||
adminClient = await makeLoggedInAdminClient() | ||
|
||
const [o10n] = await createTestOrganization(adminClient) | ||
dummyO10n = o10n | ||
|
||
staffClientWithPermissions = await makeClientWithNewRegisteredAndLoggedInUser() | ||
const [roleYes] = await createTestOrganizationEmployeeRole(adminClient, o10n, { canReadNewsItems: true }) | ||
await createTestOrganizationEmployee(adminClient, o10n, staffClientWithPermissions.user, roleYes) | ||
|
||
const [B2BAppNewsSharingConfig] = await createTestB2BAppNewsSharingConfig(adminClient, { | ||
getRecipientsUrl: `${conf['SERVER_URL']}${SUCCESS_GET_RECIPIENTS_URL}`, | ||
previewUrl: `${conf['SERVER_URL']}${SUCCESS_PREVIEW_URL}`, | ||
publishUrl: `${conf['SERVER_URL']}${SUCCESS_PUBLISH_URL}`, | ||
}) | ||
const [B2BAppWithNewsSharing] = await createTestB2BApp(adminClient, { newsSharingConfig: { connect: { id: B2BAppNewsSharingConfig.id } } }) | ||
const [B2BContextWithNewsSharingConfig] = await createTestB2BAppContext(adminClient, B2BAppWithNewsSharing, o10n) | ||
dummyB2BContextWithNewsSharingConfig = B2BContextWithNewsSharingConfig | ||
}) | ||
|
||
test('Admin can execute query', async () => { | ||
const [data, attrs] = await getNewsSharingRecipientsByTestClient(adminClient, dummyB2BContextWithNewsSharingConfig) | ||
|
||
expect(data).toMatchObject(SUCCESS_GET_RECIPIENTS_RESULT) | ||
}) | ||
|
||
test('staff with permission can execute', async () => { | ||
const [data, attrs] = await getNewsSharingRecipientsByTestClient(staffClientWithPermissions, dummyB2BContextWithNewsSharingConfig) | ||
|
||
expect(data).toMatchObject(SUCCESS_GET_RECIPIENTS_RESULT) | ||
}) | ||
|
||
test('fails if B2BContext doesnt have NewsSharingConfig', async () => { | ||
const [B2BApp] = await createTestB2BApp(adminClient) | ||
const [B2BContext] = await createTestB2BAppContext(adminClient, B2BApp, dummyO10n) | ||
|
||
await expectToThrowGQLError(async () => { | ||
await getNewsSharingRecipientsByTestClient(staffClientWithPermissions, B2BContext) | ||
}, { | ||
type: ERRORS.NOT_NEWS_SHARING_APP.type, | ||
code: ERRORS.NOT_NEWS_SHARING_APP.code, | ||
message: ERRORS.NOT_NEWS_SHARING_APP.message, | ||
}, 'result') | ||
}) | ||
|
||
test('fails if remote server return bad response code: 404', async () => { | ||
const [B2BAppFailingNewsSharingConfig] = await createTestB2BAppNewsSharingConfig(adminClient, { | ||
getRecipientsUrl: `${conf['SERVER_URL']}${FAULTY_GET_RECIPIENTS_URL_404}`, | ||
previewUrl: `${conf['SERVER_URL']}${SUCCESS_PREVIEW_URL}`, | ||
publishUrl: `${conf['SERVER_URL']}${SUCCESS_PUBLISH_URL}`, | ||
}) | ||
const [B2BApp] = await createTestB2BApp(adminClient, { newsSharingConfig: { connect: { id: B2BAppFailingNewsSharingConfig.id } } }) | ||
const [B2BContext] = await createTestB2BAppContext(adminClient, B2BApp, dummyO10n) | ||
|
||
await expectToThrowGQLError(async () => { | ||
await getNewsSharingRecipientsByTestClient(staffClientWithPermissions, B2BContext) | ||
}, { | ||
type: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.type, | ||
code: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.code, | ||
message: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.message, | ||
}, 'result') | ||
}) | ||
|
||
test('fails if remote server return bad response code: 500', async () => { | ||
const [B2BAppFailingNewsSharingConfig] = await createTestB2BAppNewsSharingConfig(adminClient, { | ||
getRecipientsUrl: `${conf['SERVER_URL']}${FAULTY_GET_RECIPIENTS_URL_500}`, | ||
previewUrl: `${conf['SERVER_URL']}${SUCCESS_PREVIEW_URL}`, | ||
publishUrl: `${conf['SERVER_URL']}${SUCCESS_PUBLISH_URL}`, | ||
}) | ||
const [B2BApp] = await createTestB2BApp(adminClient, { newsSharingConfig: { connect: { id: B2BAppFailingNewsSharingConfig.id } } }) | ||
const [B2BContext] = await createTestB2BAppContext(adminClient, B2BApp, dummyO10n) | ||
|
||
await expectToThrowGQLError(async () => { | ||
await getNewsSharingRecipientsByTestClient(staffClientWithPermissions, B2BContext) | ||
}, { | ||
type: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.type, | ||
code: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.code, | ||
message: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.message, | ||
}, 'result') | ||
}) | ||
|
||
test('fails if remote server returns wrong data', async () => { | ||
const [B2BAppFailingNewsSharingConfig] = await createTestB2BAppNewsSharingConfig(adminClient, { | ||
getRecipientsUrl: `${conf['SERVER_URL']}${INCORRECT_GET_RECIPIENTS_URL}`, | ||
previewUrl: `${conf['SERVER_URL']}${SUCCESS_PREVIEW_URL}`, | ||
publishUrl: `${conf['SERVER_URL']}${SUCCESS_PUBLISH_URL}`, | ||
}) | ||
const [B2BApp] = await createTestB2BApp(adminClient, { newsSharingConfig: { connect: { id: B2BAppFailingNewsSharingConfig.id } } }) | ||
const [B2BContext] = await createTestB2BAppContext(adminClient, B2BApp, dummyO10n) | ||
|
||
await expectToThrowGQLError(async () => { | ||
await getNewsSharingRecipientsByTestClient(staffClientWithPermissions, B2BContext) | ||
}, { | ||
type: ERRORS.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE.type, | ||
code: ERRORS.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE.code, | ||
message: ERRORS.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE.message, | ||
}, 'result') | ||
}) | ||
|
||
test('fails if remote server is inaccessible', async () => { | ||
const [B2BAppFailingNewsSharingConfig] = await createTestB2BAppNewsSharingConfig(adminClient, { | ||
getRecipientsUrl: 'https://101.101.101.101:10101', | ||
previewUrl: `${conf['SERVER_URL']}${SUCCESS_PREVIEW_URL}`, | ||
publishUrl: `${conf['SERVER_URL']}${SUCCESS_PUBLISH_URL}`, | ||
}) | ||
const [B2BApp] = await createTestB2BApp(adminClient, { newsSharingConfig: { connect: { id: B2BAppFailingNewsSharingConfig.id } } }) | ||
const [B2BContext] = await createTestB2BAppContext(adminClient, B2BApp, dummyO10n) | ||
|
||
await expectToThrowGQLError(async () => { | ||
await getNewsSharingRecipientsByTestClient(staffClientWithPermissions, B2BContext) | ||
}, { | ||
type: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.type, | ||
code: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.code, | ||
message: ERRORS.NEWS_SHARING_APP_REQUEST_FAILED.message, | ||
}, 'result') | ||
}) | ||
|
||
test('anonymous can\'t execute', async () => { | ||
const anonymousClient = await makeClient() | ||
await expectToThrowAuthenticationError(async () => { | ||
await getNewsSharingRecipientsByTestClient(anonymousClient, dummyB2BContextWithNewsSharingConfig) | ||
}, 'result') | ||
}) | ||
|
||
test('staff without permission can\'t execute', async () => { | ||
const staffClientWithoutPermissions = await makeClientWithNewRegisteredAndLoggedInUser() | ||
const [roleNo] = await createTestOrganizationEmployeeRole(adminClient, dummyO10n, { canReadNewsItems: false }) | ||
await createTestOrganizationEmployee(adminClient, dummyO10n, staffClientWithoutPermissions.user, roleNo) | ||
|
||
await expectToThrowAccessDeniedErrorToResult(async () => { | ||
await getNewsSharingRecipientsByTestClient(staffClientWithoutPermissions, dummyB2BContextWithNewsSharingConfig) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.