Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(condo): DOMA-10226 add full stacked gql error traces #5243

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5c3a0bf
refactor(condo): DOMA-10227 remove ensure-error
pahaz Sep 16, 2024
b612423
refactor(condo): DOMA-10232 cleanup apolloErrorFormatter
pahaz Sep 17, 2024
e87a6a4
fix(condo): DOMA-10227 remove apollo-server-errors
pahaz Sep 17, 2024
232be31
fix(condo): DOMA-10227 apolloError style and tests
pahaz Sep 17, 2024
0f7ba06
feat(condo): DOMA-10226 add fullstack
pahaz Sep 18, 2024
b418ee9
feat(condo): DOMA-10226 add nested error throw
pahaz Sep 18, 2024
e8239f5
refactor(condo): DOMA-10226 hide internal and development fields for …
pahaz Sep 19, 2024
bb02acf
refactor(condo): DOMA-10226 catchErrorFrom, we want to display error …
pahaz Sep 19, 2024
27c57ff
refactor(condo): DOMA-10226 refactor catchErrorFrom
pahaz Sep 19, 2024
2a46416
refactor(condo): DOMA-10226 refactor validateQRCodeByTestClient add e…
pahaz Sep 19, 2024
7a131cf
refactor(condo): DOMA-10226 improve readability
pahaz Sep 20, 2024
85e06b2
refactor(condo): DOMA-10226 improve readability and fix mistakes
pahaz Sep 20, 2024
7fa02de
refactor(condo): DOMA-10226 add todo
pahaz Sep 20, 2024
d6636dd
Merge branch 'main' into errors-gql
pahaz Sep 23, 2024
8f7a004
fix(condo): DOMA-10226 process exit 1 if any error
pahaz Sep 24, 2024
e9857ca
chore(condo): DOMA-10226 rollback changes
pahaz Sep 24, 2024
3f415dc
refactor(condo): DOMA-10226 cleanup the logic and style
pahaz Sep 24, 2024
44f0469
refactor(condo): DOMA-10232 add default messageInterpolation and use …
pahaz Sep 24, 2024
7e84977
refactor(condo): DOMA-10232 cleanup
pahaz Sep 24, 2024
f570fa2
refactor(condo): DOMA-10342 prepare for removing ensure-error
pahaz Sep 30, 2024
3f8e1bd
refactor(condo): DOMA-10226 add SUB_GQL_ERROR type
pahaz Sep 30, 2024
aa58634
docs(condo): DOMA-10226 review fixes
pahaz Oct 1, 2024
4fadf06
refactor(condo): DOMA-10226 use clear naming
pahaz Oct 1, 2024
9afd386
refactor(condo): DOMA-10226 use GQLInternalErrorTypes
pahaz Oct 1, 2024
e56abdb
refactor(condo): DOMA-10226 use GQLInternalErrorTypes
pahaz Oct 1, 2024
bb404f2
fix(condo): DOMA-10226 use warn instead of throw new Error. Wait for …
pahaz Oct 1, 2024
6a67cf5
fix(condo): DOMA-10226 @Alllex202 comment about typo
pahaz Oct 1, 2024
a01992a
fix(condo): DOMA-10226 add `data` backward compatibility field
pahaz Oct 1, 2024
dcd0a08
fix(condo): DOMA-10226 add some missed messages
pahaz Oct 1, 2024
be4e122
fix(condo): DOMA-10226 add CI diff
pahaz Oct 1, 2024
5ce1139
fix(condo): DOMA-10226 change DEFAULT_LOCALE to en
pahaz Oct 1, 2024
b2bed91
fix(condo): DOMA-10226 fix CreateArtifact: Received non-retryable err…
pahaz Oct 1, 2024
d18b334
fix(condo): DOMA-10226 some tests
pahaz Oct 1, 2024
ae80ced
fix(condo): DOMA-10226 add .errors extraction and .data field for bac…
pahaz Oct 2, 2024
5dddc1d
refactor(condo): DOMA-10226 some error checks
pahaz Oct 2, 2024
060dd1b
fix(condo): DOMA-10370 add sendMessageToSupport docs
pahaz Oct 2, 2024
d2e4f41
chore(condo): DOMA-10370 fix docs text
pahaz Oct 2, 2024
1d0a68f
chore(condo): DOMA-10226 improve compatibility and DX
pahaz Oct 3, 2024
792b79c
chore(condo): DOMA-10226 improve tooling and DX
pahaz Oct 3, 2024
6b1c27c
chore(condo): DOMA-10226 use mock for @open-condo/locales/loader
pahaz Oct 3, 2024
ab575ff
chore(condo): DOMA-10226 add backward compatibility
pahaz Oct 3, 2024
bb63f54
chore(condo): DOMA-10368 change some tests compatibility
pahaz Oct 3, 2024
5e9c119
chore(condo): DOMA-10232 ci fixes
pahaz Oct 3, 2024
2a6a1ed
chore(condo): DOMA-10232 use expectToThrowGQLErrorToResult
pahaz Oct 3, 2024
56dd62e
chore(condo): DOMA-10232 use expectToThrowGQLErrorToResult
pahaz Oct 3, 2024
d3da221
chore(condo): DOMA-10232 fix random tests
pahaz Oct 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/condo/domains/billing/schema/BillingAccount.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const {
expectToThrowAuthenticationErrorToObjects,
expectToThrowAccessDeniedErrorToObj,
expectToThrowAccessDeniedErrorToObjects,
expectToThrowInternalError,
expectToThrowGraphQLRequestError,
expectToThrowValidationFailureError,
} = require('@open-condo/keystone/test.utils')
Expand Down
1 change: 0 additions & 1 deletion apps/condo/domains/billing/schema/BillingProperty.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const {
expectToThrowAuthenticationErrorToObjects,
expectToThrowAccessDeniedErrorToObj,
expectToThrowAccessDeniedErrorToObjects,
expectToThrowInternalError,
expectToThrowGraphQLRequestError,
} = require('@open-condo/keystone/test.utils')
const { makeClient } = require('@open-condo/keystone/test.utils')
Expand Down
1 change: 1 addition & 0 deletions apps/condo/domains/billing/schema/ValidateQRCodeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ const ValidateQRCodeService = new GQLCustomSchema('ValidateQRCodeService', {
`validate-QR-code-${ip}`,
BILLING_VALIDATE_QR_CODE_WINDOW,
MAX_CLIENT_VALIDATE_QR_CODE_BY_WINDOW,
context,
)
}

Expand Down
21 changes: 9 additions & 12 deletions apps/condo/domains/billing/schema/ValidateQRCodeService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,18 +415,15 @@ describe('ValidateQRCodeService', () => {

for await (const i of Array.from(Array(MAX_CLIENT_VALIDATE_QR_CODE_BY_WINDOW + 1).keys())) {
if (i === MAX_CLIENT_VALIDATE_QR_CODE_BY_WINDOW) {
await catchErrorFrom(async () => {
await validateQRCodeByTestClient(anonymousClient, { qrCode: qrCodeString })
}, ({ errors }) => {
expect(errors).toMatchObject([{
path: ['result'],
extensions: {
code: 'BAD_USER_INPUT',
type: 'TOO_MANY_REQUESTS',
message: 'You have to wait {secondsRemaining} seconds to be able to send request again',
},
}])
})
await expectToThrowGQLError(
async () => await validateQRCodeByTestClient(anonymousClient, { qrCode: qrCodeString }),
{
code: 'BAD_USER_INPUT',
type: 'TOO_MANY_REQUESTS',
message: 'You have to wait {secondsRemaining} seconds to be able to send request again',
},
'result'
)
} else {
await validateQRCodeByTestClient(anonymousClient, { qrCode: qrCodeString })
}
Expand Down
2 changes: 1 addition & 1 deletion apps/condo/domains/billing/utils/testSchema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ async function validateQRCodeByTestClient (client, extraAttrs = {}) {
...extraAttrs,
}
const { data, errors } = await client.mutate(VALIDATE_QRCODE_MUTATION, { data: attrs })
throwIfError(data, errors)
throwIfError(data, errors, { query: VALIDATE_QRCODE_MUTATION, variables: { data: attrs } })
return [data.result, attrs]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @jest-environment node
*/

const { makeLoggedInAdminClient, makeClient, expectValuesOfCommonFields, expectToThrowGQLError, expectToThrowInternalError } = require('@open-condo/keystone/test.utils')
const { makeLoggedInAdminClient, makeClient, expectValuesOfCommonFields, expectToThrowGQLError } = require('@open-condo/keystone/test.utils')
const {
expectToThrowAuthenticationErrorToObj, expectToThrowAuthenticationErrorToObjects,
expectToThrowAccessDeniedErrorToObj,
Expand Down
2 changes: 2 additions & 0 deletions apps/condo/domains/miniapp/schema/B2BApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,12 @@ const B2BApp = new GQLListSchema('B2BApp', {
const newItem = { ...existingItem, ...resolvedData }
if (newItem.isGlobal) {
if (!newItem.appUrl) {
// TODO(pahaz): use GQLError
pahaz marked this conversation as resolved.
Show resolved Hide resolved
pahaz marked this conversation as resolved.
Show resolved Hide resolved
return addValidationError(GLOBAL_APP_NO_APP_URL_ERROR)
}
} else {
if (newItem.features) {
// TODO(pahaz): use GQLError
return addValidationError(NON_GLOBAL_APP_WITH_FEATURES_ERROR)
}
}
Expand Down
8 changes: 4 additions & 4 deletions apps/condo/domains/notification/schema/Message.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ describe('Message', () => {

await expectToThrowInternalError(
async () => await createTestMessage(admin, { user: { connect: { id: user.user.id } }, uniqKey }),
DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE,
`${DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE} "message_unique_user_type_uniqKey"`,
)
})

Expand All @@ -214,7 +214,7 @@ describe('Message', () => {

await expectToThrowInternalError(
async () => await createTestMessage(admin, { user: null, uniqKey }),
DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE,
`${DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE} "message_unique_type_uniqKey"`,
)
})

Expand All @@ -233,12 +233,12 @@ describe('Message', () => {

await expectToThrowInternalError(
async () => await createTestMessage(admin, { user: { connect: { id: user.user.id } }, uniqKey }),
DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE,
`${DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE} "message_unique_user_type_uniqKey"`,
)

await expectToThrowInternalError(
async () => await createTestMessage(admin, { uniqKey }),
DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE,
`${DUPLICATE_CONSTRAINT_VIOLATION_ERROR_MESSAGE} "message_unique_type_uniqKey"`,
)
})

Expand Down
5 changes: 3 additions & 2 deletions apps/condo/domains/user/constants/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const PASSWORD_CONSISTS_OF_SMALL_SET_OF_CHARACTERS = 'PASSWORD_CONSISTS_OF_SMALL

const GQL_ERRORS = {
TOO_MANY_REQUESTS: {
// TODO(pahaz): it' looks like a wrong code. Use TOO_MANY_REQUESTS
pahaz marked this conversation as resolved.
Show resolved Hide resolved
code: 'BAD_USER_INPUT',
type: TOO_MANY_REQUESTS,
message: 'You have to wait {secondsRemaining} seconds to be able to send request again',
Expand Down Expand Up @@ -105,7 +106,7 @@ const GQL_ERRORS = {
variable: ['data', 'password'],
code: 'BAD_USER_INPUT',
type: INVALID_PASSWORD_LENGTH,
message: `Password length must be between ${MIN_PASSWORD_LENGTH} and ${MAX_PASSWORD_LENGTH} characters`,
message: 'Password length must be between {min} and {max} characters',
messageForUser: 'api.user.INVALID_PASSWORD_LENGTH',
messageInterpolation: {
min: MIN_PASSWORD_LENGTH,
Expand Down Expand Up @@ -137,7 +138,7 @@ const GQL_ERRORS = {
variable: ['data', 'password'],
code: 'BAD_USER_INPUT',
type: PASSWORD_CONSISTS_OF_SMALL_SET_OF_CHARACTERS,
message: `Password must contain at least ${MIN_COUNT_OF_DIFFERENT_CHARACTERS_IN_PASSWORD} different characters`,
message: 'Password must contain at least {min} different characters',
messageForUser: 'api.user.PASSWORD_CONSISTS_OF_SMALL_SET_OF_CHARACTERS',
messageInterpolation: {
min: MIN_COUNT_OF_DIFFERENT_CHARACTERS_IN_PASSWORD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { faker } = require('@faker-js/faker')
const {
makeLoggedInAdminClient,
makeClient,
expectToThrowInternalError,
expectToThrowGQLError,
catchErrorFrom,
expectToThrowAccessDeniedErrorToResult,
expectToThrowAuthenticationErrorToResult,
Expand All @@ -22,7 +22,6 @@ const {
changePhoneNumberResidentUserByTestClient,
} = require('@condo/domains/user/utils/testSchema')


describe('ChangePhoneNumberResidentUserService', () => {
describe('Anonymous', () => {
it('can not change phone with token', async () => {
Expand Down Expand Up @@ -72,9 +71,15 @@ describe('ChangePhoneNumberResidentUserService', () => {
const client = await makeClientWithResidentUser({ type: RESIDENT, isPhoneVerified: true })
await createTestUser(admin, { phone: phone, type: RESIDENT })

await expectToThrowInternalError(async () => {
await changePhoneNumberResidentUserByTestClient(client, { token })
}, '[error] Update User internal error', 'result')
await expectToThrowGQLError(
async () => await changePhoneNumberResidentUserByTestClient(client, { token }),
{
code: 'INTERNAL_ERROR',
type: 'SUB_GQL_ERROR',
pahaz marked this conversation as resolved.
Show resolved Hide resolved
message: '[error] Update User internal error',
},
'result',
)
})
it('can not change phone with expired token', async () => {
const admin = await makeLoggedInAdminClient()
Expand Down
16 changes: 8 additions & 8 deletions packages/codegen/generate.server.utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { pickBy, get, isEmpty, isObject } = require('lodash')

const conf = require('@open-condo/config')
const { GQLError } = require('@open-condo/keystone/errors')
const { GQLError, GQLErrorCode } = require('@open-condo/keystone/errors')
const { getById } = require('@open-condo/keystone/schema')

const IS_DEBUG = conf.NODE_ENV === 'development'
Expand All @@ -26,8 +26,6 @@ function _throwIfError (context, errors, data, errorMessage, errorMapping) {
console.error(errors)
}

let error = new Error(errorMessage)

/** NOTE(pahaz): you can use it like so:
*
* const ERRORS = {
Expand All @@ -51,20 +49,22 @@ function _throwIfError (context, errors, data, errorMessage, errorMapping) {
* })
*
*/
const fields = {
code: GQLErrorCode.INTERNAL_ERROR,
type: 'SUB_GQL_ERROR',
pahaz marked this conversation as resolved.
Show resolved Hide resolved
message: errorMessage,
}
if (errorMapping && isObject(errorMapping)) {
const message = _getAllErrorMessages(errors).join(' -- ')
for (const key in errorMapping) {
if (message.includes(key)) {
error = new GQLError(errorMapping[key], context)
Object.assign(fields, errorMapping[key])
break
}
}
}

// NOTE(pahaz): we will see this nested result at the ApolloErrorFormatter
error.errors = errors
error.reqId = get(context, 'req.id')
throw error
throw new GQLError(fields, context, errors)
}
if (!data || typeof data !== 'object') {
throw new Error('wrong query result')
Expand Down
31 changes: 25 additions & 6 deletions packages/codegen/generate.test.utils.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
const { faker } = require('@faker-js/faker')
const { print } = require('graphql')
const { get, isEmpty } = require('lodash')
const { get, isEmpty, pick, map, toArray } = require('lodash')

const { normalizeQuery } = require('@open-condo/keystone/logging/normalize')

class TestClientResponseError extends Error {
constructor (data, errors, context = {}) {
super(`Test client caught GraphQL response with not empty errors body! ${_renderDeveloperFriendlyErrorMessage(data, errors, context)}`)
this.errors = errors
this.data = data
this.context = context
// NOTE(pahaz): we don't want to display all this fields during console.error
this._constructor = { data, errors, context }
this.name = 'TestClientResponseError'
}

get data () {
return this._constructor?.data
}

get errors () {
// NOTE(pahaz): we want to use only public error fields during the rests
return map(toArray(this._constructor?.errors), error => pick(error, ['message', 'name', 'locations', 'path', 'extensions']))
}
}

function throwIfError (data, errors, context = {}) {
Expand Down Expand Up @@ -77,6 +85,17 @@ function _renderDeveloperFriendlyErrorMessage (data, errors, context) {
msg.push('ERRORS: no or empty')
}

if (errors) {
msg.push('--stack--')
const length = errors.length
errors.forEach((error, index) => {
const prefix = (length === 1) ? '' : `STACK[${index + 1}/${length}]: `
const stack = error?.fullstack || error.stack
msg.push(prefix + stack)
})
}

msg.push('--end--\n')
return msg.join('\n')
}

Expand Down Expand Up @@ -126,7 +145,7 @@ function generateGQLTestUtils (gql) {

async function createMany (client, attrsArray = [], { raw = false } = {}) {
_checkClient(client)
const { data, errors } = await client.mutate(gql.CREATE_OBJS_MUTATION, {
const { data, errors } = await client.mutate(gql.CREATE_OBJS_MUTATION, {
data: [...attrsArray],
})
if (raw) return { data, errors }
Expand All @@ -147,7 +166,7 @@ function generateGQLTestUtils (gql) {
async function updateMany (client, attrsArray = [], { raw = false } = {}) {
_checkClient(client)
const { data, errors } = await client.mutate(gql.UPDATE_OBJS_MUTATION, {
data: [ ...attrsArray ],
data: [...attrsArray],
})
if (raw) return { data, errors }
throwIfError(data, errors, { query: gql.UPDATE_OBJS_MUTATION, variables: { data: [...attrsArray] } })
Expand Down
Loading
Loading