[OBJECT_MANIFEST_BREAKING_CHANGE] Sync returns workspace migration (#17918)
# Introduction In this PR we start returning a workspace migration post sync so it can committed and provided within the tarball ## Universal aggregators utils Created two utils ### deleteUniversalFlatEntityForeignKeyAggregators Used when building a universal create action, a newly created actions should not contain any aggregated foreign key so they won't be codegen in the workspace migration but also they are overriden at uninversal to flat transpilation anw ### resetUniversalFlatEntityForeignKeyAggregators Used before validating a new flat entity creation, some validator will consume the fk aggregator in order to validate integrity, but of optimstically provided it can result to errors. To avoid caller responsability we override them here ## create-field-action refactor Refactored the universal and flat field create action to be following the base actions in order to ease typing Also it was tailored to handle unlimited amount of flat field metadata in the same actions whereas in the reality we were always only sending at max 2 ( for relation fields ) Note: relation field has to be provided at the same as if not optimistic would fail to retrieve circular universal identifiers ## ObjectManifest Now always expect a `labelIdentifierFieldMetadataUniversalIdentifier` ## Integration test Created an integration test that creates an app, sync a first manifest and a second implying update workspace migration action generation
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`syncApplication should return workspace migration actions on initial sync then on second sync with field rename and new role 1`] = `
|
||||
{
|
||||
"syncApplication": {
|
||||
"actions": [
|
||||
{
|
||||
"flatEntity": {
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"createdAt": Any<String>,
|
||||
"description": "A support ticket",
|
||||
"duplicateCriteria": null,
|
||||
"icon": "IconTicket",
|
||||
"imageIdentifierFieldMetadataUniversalIdentifier": null,
|
||||
"isActive": true,
|
||||
"isAuditLogged": true,
|
||||
"isCustom": true,
|
||||
"isLabelSyncedWithName": true,
|
||||
"isRemote": false,
|
||||
"isSearchable": false,
|
||||
"isSystem": false,
|
||||
"isUIReadOnly": false,
|
||||
"labelIdentifierFieldMetadataUniversalIdentifier": Any<String>,
|
||||
"labelPlural": "Tickets",
|
||||
"labelSingular": "Ticket",
|
||||
"namePlural": "tickets",
|
||||
"nameSingular": "ticket",
|
||||
"shortcut": null,
|
||||
"standardOverrides": null,
|
||||
"targetTableName": "DEPRECATED",
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
"metadataName": "objectMetadata",
|
||||
"type": "create",
|
||||
"universalFlatFieldMetadatas": [
|
||||
{
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"createdAt": Any<String>,
|
||||
"defaultValue": null,
|
||||
"description": "Ticket description",
|
||||
"icon": "IconFileDescription",
|
||||
"isActive": true,
|
||||
"isCustom": true,
|
||||
"isLabelSyncedWithName": false,
|
||||
"isNullable": true,
|
||||
"isSystem": false,
|
||||
"isUIReadOnly": false,
|
||||
"isUnique": false,
|
||||
"label": "Description",
|
||||
"morphId": null,
|
||||
"name": "description",
|
||||
"objectMetadataUniversalIdentifier": Any<String>,
|
||||
"options": null,
|
||||
"relationTargetFieldMetadataUniversalIdentifier": null,
|
||||
"relationTargetObjectMetadataUniversalIdentifier": null,
|
||||
"standardOverrides": null,
|
||||
"type": "TEXT",
|
||||
"universalIdentifier": Any<String>,
|
||||
"universalSettings": null,
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"flatEntity": {
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"canAccessAllTools": false,
|
||||
"canBeAssignedToAgents": true,
|
||||
"canBeAssignedToApiKeys": true,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canReadAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"createdAt": Any<String>,
|
||||
"description": "A test role",
|
||||
"icon": null,
|
||||
"isEditable": true,
|
||||
"label": "Test Role",
|
||||
"rowLevelPermissionPredicateGroupUniversalIdentifiers": [],
|
||||
"rowLevelPermissionPredicateUniversalIdentifiers": [],
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
"metadataName": "role",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`syncApplication should return workspace migration actions on initial sync then on second sync with field rename and new role 2`] = `
|
||||
{
|
||||
"syncApplication": {
|
||||
"actions": [
|
||||
{
|
||||
"metadataName": "fieldMetadata",
|
||||
"type": "update",
|
||||
"universalIdentifier": Any<String>,
|
||||
"update": {
|
||||
"label": "Body",
|
||||
"name": "body",
|
||||
},
|
||||
},
|
||||
{
|
||||
"flatEntity": {
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"canAccessAllTools": false,
|
||||
"canBeAssignedToAgents": true,
|
||||
"canBeAssignedToApiKeys": true,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canReadAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"createdAt": Any<String>,
|
||||
"description": "A read-only role",
|
||||
"icon": null,
|
||||
"isEditable": true,
|
||||
"label": "Viewer Role",
|
||||
"rowLevelPermissionPredicateGroupUniversalIdentifiers": [],
|
||||
"rowLevelPermissionPredicateUniversalIdentifiers": [],
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
"metadataName": "role",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
},
|
||||
}
|
||||
`;
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { createOneApplication } from 'test/integration/metadata/suites/application/utils/create-one-application.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
|
||||
import { uploadApplicationFile } from 'test/integration/metadata/suites/application/utils/upload-application-file.util';
|
||||
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
|
||||
|
||||
const TEST_APP_ID = uuidv4();
|
||||
const TEST_ROLE_ID = uuidv4();
|
||||
const TEST_SECOND_ROLE_ID = uuidv4();
|
||||
const TEST_OBJECT_ID = uuidv4();
|
||||
const TEST_FIELD_ID = uuidv4();
|
||||
|
||||
describe('syncApplication', () => {
|
||||
let appCreated = false;
|
||||
|
||||
beforeAll(async () => {
|
||||
await createOneApplication({
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
name: 'Test Application',
|
||||
description: 'A test application',
|
||||
version: '1.0.0',
|
||||
sourcePath: 'test-sync',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
appCreated = true;
|
||||
|
||||
// File upload uses multipart which requires real timers
|
||||
jest.useRealTimers();
|
||||
|
||||
const packageJson = JSON.stringify({
|
||||
name: 'test-application',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
await uploadApplicationFile({
|
||||
applicationUniversalIdentifier: TEST_APP_ID,
|
||||
fileFolder: 'Dependencies',
|
||||
filePath: 'package.json',
|
||||
fileBuffer: Buffer.from(packageJson),
|
||||
filename: 'package.json',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
jest.useFakeTimers();
|
||||
}, 60000);
|
||||
|
||||
afterAll(async () => {
|
||||
if (!appCreated) {
|
||||
return;
|
||||
}
|
||||
|
||||
await uninstallApplication({
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return workspace migration actions on initial sync then on second sync with field rename and new role', async () => {
|
||||
const initialManifest: Manifest = {
|
||||
application: {
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
defaultRoleUniversalIdentifier: TEST_ROLE_ID,
|
||||
displayName: 'Test Application',
|
||||
description: 'A test application for workspace migration',
|
||||
icon: 'IconTestPipe',
|
||||
applicationVariables: {},
|
||||
packageJsonChecksum: null,
|
||||
yarnLockChecksum: null,
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
],
|
||||
objects: [
|
||||
{
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: TEST_FIELD_ID,
|
||||
universalIdentifier: TEST_OBJECT_ID,
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
fields: [],
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT_ID,
|
||||
},
|
||||
],
|
||||
logicFunctions: [],
|
||||
frontComponents: [],
|
||||
publicAssets: [],
|
||||
};
|
||||
|
||||
const { data: firstSyncData } = await syncApplication({
|
||||
manifest: initialManifest,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(firstSyncData).toMatchSnapshot(
|
||||
extractRecordIdsAndDatesAsExpectAny(firstSyncData),
|
||||
);
|
||||
|
||||
const updatedManifest: Manifest = {
|
||||
...initialManifest,
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
{
|
||||
universalIdentifier: TEST_SECOND_ROLE_ID,
|
||||
label: 'Viewer Role',
|
||||
description: 'A read-only role',
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT_ID,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data: secondSyncData } = await syncApplication({
|
||||
manifest: updatedManifest,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(secondSyncData).toMatchSnapshot(
|
||||
extractRecordIdsAndDatesAsExpectAny(secondSyncData),
|
||||
);
|
||||
}, 60000);
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const createOneApplicationQueryFactory = ({
|
||||
universalIdentifier,
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
sourcePath,
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
version: string;
|
||||
sourcePath: string;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation CreateOneApplication($input: CreateApplicationInput!) {
|
||||
createOneApplication(input: $input) {
|
||||
id
|
||||
universalIdentifier
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
universalIdentifier,
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
sourcePath,
|
||||
},
|
||||
},
|
||||
});
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { createOneApplicationQueryFactory } from 'test/integration/metadata/suites/application/utils/create-one-application-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
type CreatedApplication = {
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const createOneApplication = async ({
|
||||
universalIdentifier,
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
sourcePath,
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
version: string;
|
||||
sourcePath: string;
|
||||
expectToFail?: boolean;
|
||||
token?: string;
|
||||
}): CommonResponseBody<{
|
||||
createOneApplication: CreatedApplication;
|
||||
}> => {
|
||||
const graphqlOperation = createOneApplicationQueryFactory({
|
||||
universalIdentifier,
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
sourcePath,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation, token);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Create one application should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Create one application has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
|
||||
export const syncApplicationQueryFactory = ({
|
||||
manifest,
|
||||
}: {
|
||||
manifest: Manifest;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation SyncApplication($manifest: JSON!) {
|
||||
syncApplication(manifest: $manifest) {
|
||||
applicationUniversalIdentifier
|
||||
actions
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
manifest,
|
||||
},
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { syncApplicationQueryFactory } from 'test/integration/metadata/suites/application/utils/sync-application-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
type WorkspaceMigration = {
|
||||
applicationUniversalIdentifier: string;
|
||||
actions: unknown[];
|
||||
};
|
||||
|
||||
export const syncApplication = async ({
|
||||
manifest,
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: {
|
||||
manifest: Manifest;
|
||||
expectToFail?: boolean;
|
||||
token?: string;
|
||||
}): CommonResponseBody<{
|
||||
syncApplication: WorkspaceMigration;
|
||||
}> => {
|
||||
const graphqlOperation = syncApplicationQueryFactory({
|
||||
manifest,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation, token);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Sync application should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Sync application has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const uninstallApplicationQueryFactory = ({
|
||||
universalIdentifier,
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation UninstallApplication($universalIdentifier: String!) {
|
||||
uninstallApplication(universalIdentifier: $universalIdentifier)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
universalIdentifier,
|
||||
},
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { uninstallApplicationQueryFactory } from 'test/integration/metadata/suites/application/utils/uninstall-application-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const uninstallApplication = async ({
|
||||
universalIdentifier,
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
expectToFail?: boolean;
|
||||
token?: string;
|
||||
}): CommonResponseBody<{
|
||||
uninstallApplication: boolean;
|
||||
}> => {
|
||||
const graphqlOperation = uninstallApplicationQueryFactory({
|
||||
universalIdentifier,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation, token);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Uninstall application should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Uninstall application has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const uploadApplicationFileQueryFactory = ({
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
filePath,
|
||||
}: {
|
||||
applicationUniversalIdentifier: string;
|
||||
fileFolder: string;
|
||||
filePath: string;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation UploadApplicationFile(
|
||||
$file: Upload!
|
||||
$applicationUniversalIdentifier: String!
|
||||
$fileFolder: FileFolder!
|
||||
$filePath: String!
|
||||
) {
|
||||
uploadApplicationFile(
|
||||
file: $file
|
||||
applicationUniversalIdentifier: $applicationUniversalIdentifier
|
||||
fileFolder: $fileFolder
|
||||
filePath: $filePath
|
||||
) {
|
||||
id
|
||||
path
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
file: null,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
filePath,
|
||||
},
|
||||
});
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { uploadApplicationFileQueryFactory } from 'test/integration/metadata/suites/application/utils/upload-application-file-query-factory.util';
|
||||
import { makeMetadataAPIRequestWithFileUpload } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-file-upload.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
type UploadedFile = {
|
||||
id: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export const uploadApplicationFile = async ({
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
filePath,
|
||||
fileBuffer,
|
||||
filename,
|
||||
contentType = 'application/json',
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: {
|
||||
applicationUniversalIdentifier: string;
|
||||
fileFolder: string;
|
||||
filePath: string;
|
||||
fileBuffer: Buffer;
|
||||
filename: string;
|
||||
contentType?: string;
|
||||
expectToFail?: boolean;
|
||||
token?: string;
|
||||
}): CommonResponseBody<{
|
||||
uploadApplicationFile: UploadedFile;
|
||||
}> => {
|
||||
const graphqlOperation = uploadApplicationFileQueryFactory({
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
filePath,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequestWithFileUpload(
|
||||
graphqlOperation,
|
||||
{
|
||||
field: 'file',
|
||||
buffer: fileBuffer,
|
||||
filename,
|
||||
contentType,
|
||||
},
|
||||
token,
|
||||
);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Upload application file should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Upload application file has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
Reference in New Issue
Block a user