Skip to content

Commit

Permalink
Merge pull request #5463 from MacondoExpress/chore-b6
Browse files Browse the repository at this point in the history
Move tests to v6, add error on conflicting properties
  • Loading branch information
angrykoala authored Aug 16, 2024
2 parents 75d7715 + 658bfc2 commit 14e1ede
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type { V6ReadOperation } from "../queryIR/ConnectionReadOperation";
import { V6CreateOperation } from "../queryIR/CreateOperation";
import { ReadOperationFactory } from "./ReadOperationFactory";
import type { GraphQLTreeCreate, GraphQLTreeCreateInput } from "./resolve-tree-parser/graphql-tree/graphql-tree";
import { raiseOnConflictingInput } from "./utils/raise-on-conflicting-input";
import { getAttribute } from "./utils/get-attribute";

export class CreateOperationFactory {
Expand Down Expand Up @@ -71,6 +72,7 @@ export class CreateOperationFactory {
entity,
});
}

const inputFields = this.getInputFields({
target: targetAdapter,
createInput: topLevelCreateInput,
Expand All @@ -92,10 +94,14 @@ export class CreateOperationFactory {
target: ConcreteEntityAdapter;
createInput: GraphQLTreeCreateInput[];
}): InputField[] {
// inputFieldsExistence is used to keep track of the fields that have been added to the inputFields array
// as with the unwind clause we define a single tree for multiple inputs
// this is to avoid adding the same field multiple times
const inputFieldsExistence = new Set<string>();
const inputFields: InputField[] = [];
inputFields.push(...this.addAutogeneratedFields(target, inputFieldsExistence));
const inputFields: InputField[] = this.addAutogeneratedFields(target, inputFieldsExistence);

for (const inputItem of createInput) {
raiseOnConflictingInput(inputItem, target.entity);
for (const key of Object.keys(inputItem)) {
const attribute = getAttribute(target, key);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { ConcreteEntity } from "../../../schema-model/entity/ConcreteEntity";
import type { Relationship } from "../../../schema-model/relationship/Relationship";
import { FactoryParseError } from "../factory-parse-error";
import type { GraphQLTreeCreateInput } from "../resolve-tree-parser/graphql-tree/graphql-tree";

export function raiseOnConflictingInput(
input: GraphQLTreeCreateInput, // TODO: add Update types as well
entityOrRel: ConcreteEntity | Relationship
): void {
const hash = {};
const properties = Object.keys(input);
properties.forEach((property) => {
const dbName = entityOrRel.findAttribute(property)?.databaseName;
if (dbName === undefined) {
throw new FactoryParseError(`Impossible to translate property ${property} on entity ${entityOrRel.name}`);
}
if (hash[dbName]) {
throw new FactoryParseError(
`Conflicting modification of ${[hash[dbName], property].map((n) => `[[${n}]]`).join(", ")} on type ${
entityOrRel.name
}`
);
}
hash[dbName] = property;
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { GraphQLError } from "graphql";
import type { UniqueType } from "../../../../utils/graphql-types";
import { TestHelper } from "../../../../utils/tests-helper";

describe("conflicting properties", () => {
const testHelper = new TestHelper({ v6Api: true });

let typeMovie: UniqueType;
let typeDirector: UniqueType;

beforeEach(async () => {
typeMovie = testHelper.createUniqueType("Movie");
typeDirector = testHelper.createUniqueType("Director");

const typeDefs = /* GraphQL */ `
type ${typeDirector} @node {
name: String
nameAgain: String @alias(property: "name")
movies: [${typeMovie}!]! @relationship(direction: OUT, type: "DIRECTED", properties: "Directed")
}
type Directed @relationshipProperties {
year: Int!
movieYear: Int @alias(property: "year")
}
type ${typeMovie} @node {
title: String
titleAgain: String @alias(property: "title")
directors: [${typeDirector}!]! @relationship(direction: IN, type: "DIRECTED", properties: "Directed")
}
`;

await testHelper.initNeo4jGraphQL({
typeDefs,
});
});

afterEach(async () => {
await testHelper.close();
});

test("Create mutation with alias referring to existing field, include both fields as inputs", async () => {
const userMutation = /* GraphQL */ `
mutation {
${typeDirector.operations.create}(input: [{ node: { name: "Tim Burton", nameAgain: "Timmy Burton" }}]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeDefined();
expect(gqlResult.errors).toHaveLength(1);
expect(gqlResult.errors).toEqual([
new GraphQLError(`Conflicting modification of [[name]], [[nameAgain]] on type ${typeDirector.name}`),
]);
expect(gqlResult?.data).toEqual({
[typeDirector.operations.create]: null,
});
});

test("Create mutation with alias referring to existing field, include only field as inputs", async () => {
const userMutation = /* GraphQL */ `
mutation {
${typeDirector.operations.create}(input: [{ node: {name: "Tim Burton"} }]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeUndefined();
expect(gqlResult?.data).toEqual({
[typeDirector.operations.create]: {
[typeDirector.plural]: [
{
name: "Tim Burton",
nameAgain: "Tim Burton",
},
],
},
});
});

test("Create mutation with alias referring to existing field, include only alias field as inputs", async () => {
const userMutation = /* GraphQL */ `
mutation {
${typeDirector.operations.create}(input: [{ node: { nameAgain: "Tim Burton" } }]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeUndefined();
expect(gqlResult?.data).toEqual({
[typeDirector.operations.create]: {
[typeDirector.plural]: [
{
name: "Tim Burton",
nameAgain: "Tim Burton",
},
],
},
});
});

test("Create mutation with alias referring to existing field, include both bad and good inputs", async () => {
const userMutation = /* GraphQL */ `
mutation {
${typeDirector.operations.create}(input: [{ node: {name: "Tim Burton", nameAgain: "Timmy Burton"} }, { node: { name: "Someone" }}]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeDefined();
expect(gqlResult.errors).toHaveLength(1);
expect(gqlResult.errors).toEqual([
new GraphQLError(`Conflicting modification of [[name]], [[nameAgain]] on type ${typeDirector.name}`),
]);
expect(gqlResult?.data).toEqual({
[typeDirector.operations.create]: null,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { generate } from "randomstring";
import type { UniqueType } from "../utils/graphql-types";
import { TestHelper } from "../utils/tests-helper";

describe("Default values", () => {
describe("@cypher default values", () => {
const testHelper = new TestHelper();
let Movie: UniqueType;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,92 +58,8 @@ describe("@alias directive", () => {
await testHelper.close();
});

test("Create mutation with alias referring to existing field, include both fields as inputs", async () => {
const userMutation = `
mutation {
${typeDirector.operations.create}(input: [{ name: "Tim Burton", nameAgain: "Timmy Burton" }]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeDefined();
expect(gqlResult.errors).toHaveLength(1);
expect(gqlResult.errors?.[0]?.message).toBe(
`Conflicting modification of [[name]], [[nameAgain]] on type ${typeDirector.name}`
);
expect(gqlResult?.data?.[typeDirector.operations.create]?.[typeDirector.plural]).toBeUndefined();
});
test("Create mutation with alias referring to existing field, include only field as inputs", async () => {
const userMutation = `
mutation {
${typeDirector.operations.create}(input: [{ name: "Tim Burton" }]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeUndefined();
expect(gqlResult?.data?.[typeDirector.operations.create]?.[typeDirector.plural]).toEqual([
{
name: "Tim Burton",
nameAgain: "Tim Burton",
},
]);
});
test("Create mutation with alias referring to existing field, include only alias field as inputs", async () => {
const userMutation = `
mutation {
${typeDirector.operations.create}(input: [{ nameAgain: "Timmy Burton" }]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeUndefined();
expect(gqlResult?.data?.[typeDirector.operations.create]?.[typeDirector.plural]).toEqual([
{
name: "Timmy Burton",
nameAgain: "Timmy Burton",
},
]);
});
test("Create mutation with alias referring to existing field, include both bad and good inputs", async () => {
const userMutation = `
mutation {
${typeDirector.operations.create}(input: [{ name: "Tim Burton", nameAgain: "Timmy Burton" }, { name: "Someone" }]) {
${typeDirector.plural} {
name
nameAgain
}
}
}
`;

const gqlResult = await testHelper.executeGraphQL(userMutation);

expect(gqlResult.errors).toBeDefined();
expect(gqlResult.errors).toHaveLength(1);
expect(gqlResult.errors?.[0]?.message).toBe(
`Conflicting modification of [[name]], [[nameAgain]] on type ${typeDirector.name}`
);
expect(gqlResult?.data?.[typeDirector.operations.create]?.[typeDirector.plural]).toBeUndefined();
});

test("Create mutation with alias on connection referring to existing field, include only field as inputs", async () => {
const userMutation = `
mutation {
Expand Down
Loading

0 comments on commit 14e1ede

Please sign in to comment.