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

Update the implementation of relationship in adapters #2000

Merged
merged 1 commit into from
Apr 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions .changeset/silent-eyes-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@keystonejs/adapter-knex': major
'@keystonejs/adapter-mongoose': major
'@keystonejs/fields': major
'@keystonejs/keystone': major
'@keystonejs/mongo-join-builder': major
---

## Release - Arcade

This release introduces a **new and improved data schema** for Keystone.
The new data schema simplifies the way your data is stored and will unlock the development of new functionality within Keystone.

> **Important:** You will need to make changes to your database to take advantage of the new data schema. Please read the full [release notes](https://www.keystonejs.com/discussions/new-data-schema) for instructions on updating your database.
378 changes: 378 additions & 0 deletions api-tests/relationships/crud-self-ref/many-to-many-one-sided.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
const { gen, sampleOne } = require('testcheck');
const { Text, Relationship } = require('@keystonejs/fields');
const cuid = require('cuid');
const { multiAdapterRunners, setupServer, graphqlRequest } = require('@keystonejs/test-utils');

const alphanumGenerator = gen.alphaNumString.notEmpty();

jest.setTimeout(6000000);

const createInitialData = async keystone => {
const { data } = await graphqlRequest({
keystone,
query: `
mutation {
createUsers(data: [{ data: { name: "${sampleOne(
alphanumGenerator
)}" } }, { data: { name: "${sampleOne(alphanumGenerator)}" } }, { data: { name: "${sampleOne(
alphanumGenerator
)}" } }]) { id }
}
`,
});
return { users: data.createUsers };
};

const createUserAndFriend = async keystone => {
const {
data: { createUser },
} = await graphqlRequest({
keystone,
query: `
mutation {
createUser(data: {
friends: { create: [{ name: "${sampleOne(alphanumGenerator)}" }] }
}) { id friends { id } }
}`,
});
const { User, Friend } = await getUserAndFriend(
keystone,
createUser.id,
createUser.friends[0].id
);

// Sanity check the links are setup correctly
expect(User.friends.map(({ id }) => id.toString())).toStrictEqual([Friend.id.toString()]);

return { user: createUser, friend: createUser.friends[0] };
};

const getUserAndFriend = async (keystone, userId, friendId) => {
const { data } = await graphqlRequest({
keystone,
query: `
{
User(where: { id: "${userId}"} ) { id friends { id } }
Friend: User(where: { id: "${friendId}"} ) { id }
}`,
});
return data;
};

const createReadData = async keystone => {
// create locations [A, A, B, B, C, C];
const { data } = await graphqlRequest({
keystone,
query: `mutation create($users: [UsersCreateInput]) { createUsers(data: $users) { id name } }`,
variables: {
users: ['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D', 'E'].map(name => ({ data: { name } })),
},
});
const { createUsers } = data;
await Promise.all(
[
[0, 1, 2, 3, 4, 5], // -> (A1) -> [A, A, B, B, C, C]
[0, 2, 4], // -> (A2) -> [A, B, C]
[0, 1], // -> (B1) -> [A, A]
[0, 2], // -> (B2) -> [A, B]
[0, 4], // -> (C1) -> [A, C]
[2, 3], // -> (C2) -> [B, B]
[0], // -> (D1) -> [A]
[2], // -> (D2) -> [B]
[], // -> (E1) -> []
].map(async (locationIdxs, j) => {
const ids = locationIdxs.map(i => ({ id: createUsers[i].id }));
const { data } = await graphqlRequest({
keystone,
query: `mutation update($friends: [UserWhereUniqueInput], $user: ID!) { updateUser(id: $user data: {
friends: { connect: $friends }
}) { id friends { name }}}`,
variables: { friends: ids, user: createUsers[j].id },
});
return data.updateUser;
})
);
};

multiAdapterRunners().map(({ runner, adapterName }) =>
describe(`Adapter: ${adapterName}`, () => {
// 1:1 relationships are symmetric in how they behave, but
// are (in general) implemented in a non-symmetric way. For example,
// in postgres we may decide to store a single foreign key on just
// one of the tables involved. As such, we want to ensure that our
// tests work correctly no matter which side of the relationship is
// defined first.
const createUserList = keystone =>
keystone.createList('User', {
fields: {
name: { type: Text },
friends: { type: Relationship, ref: 'User', many: true },
},
});
const createLists = createUserList;

describe(`Many-to-many relationships`, () => {
function setupKeystone(adapterName) {
return setupServer({
adapterName,
name: `ks5-testdb-${cuid()}`,
createLists,
});
}

describe('Read', () => {
test(
'_some',
runner(setupKeystone, async ({ keystone }) => {
await createReadData(keystone);
await Promise.all(
[
['A', 6],
['B', 5],
['C', 3],
['D', 0],
].map(async ([name, count]) => {
const { data } = await graphqlRequest({
keystone,
query: `{ allUsers(where: { friends_some: { name: "${name}"}}) { id }}`,
});
expect(data.allUsers.length).toEqual(count);
})
);
})
);
test(
'_none',
runner(setupKeystone, async ({ keystone }) => {
await createReadData(keystone);
await Promise.all(
[
['A', 3],
['B', 4],
['C', 6],
['D', 9],
].map(async ([name, count]) => {
const { data } = await graphqlRequest({
keystone,
query: `{ allUsers(where: { friends_none: { name: "${name}"}}) { id }}`,
});
expect(data.allUsers.length).toEqual(count);
})
);
})
);
test(
'_every',
runner(setupKeystone, async ({ keystone }) => {
await createReadData(keystone);
await Promise.all(
[
['A', 3],
['B', 3],
['C', 1],
['D', 1],
].map(async ([name, count]) => {
const { data } = await graphqlRequest({
keystone,
query: `{ allUsers(where: { friends_every: { name: "${name}"}}) { id }}`,
});
expect(data.allUsers.length).toEqual(count);
})
);
})
);
});

describe('Create', () => {
test(
'With connect',
runner(setupKeystone, async ({ keystone }) => {
const { users } = await createInitialData(keystone);
const user = users[0];
const { data, errors } = await graphqlRequest({
keystone,
query: `
mutation {
createUser(data: {
friends: { connect: [{ id: "${user.id}" }] }
}) { id friends { id } }
}
`,
});
expect(errors).toBe(undefined);
expect(data.createUser.friends.map(({ id }) => id.toString())).toEqual([user.id]);

const { User, Friend } = await getUserAndFriend(keystone, data.createUser.id, user.id);
// Everything should now be connected
expect(User.friends.map(({ id }) => id.toString())).toEqual([Friend.id.toString()]);
})
);

test(
'With create',
runner(setupKeystone, async ({ keystone }) => {
const friendName = sampleOne(alphanumGenerator);
const { data, errors } = await graphqlRequest({
keystone,
query: `
mutation {
createUser(data: {
friends: { create: [{ name: "${friendName}" }] }
}) { id friends { id } }
}
`,
});
expect(errors).toBe(undefined);

const { User, Friend } = await getUserAndFriend(
keystone,
data.createUser.id,
data.createUser.friends[0].id
);

// Everything should now be connected
expect(User.friends.map(({ id }) => id.toString())).toEqual([Friend.id.toString()]);
})
);
});

describe('Update', () => {
test(
'With connect',
runner(setupKeystone, async ({ keystone }) => {
// Manually setup a connected Company <-> Location
const { user, friend } = await createUserAndFriend(keystone);

// Sanity check the links don't yet exist
// `...not.toBe(expect.anything())` allows null and undefined values
expect(user.friends).not.toBe(expect.anything());

const { errors } = await graphqlRequest({
keystone,
query: `
mutation {
updateUser(
id: "${user.id}",
data: { friends: { connect: [{ id: "${friend.id}" }] } }
) { id friends { id } } }
`,
});
expect(errors).toBe(undefined);

const { User, Friend } = await getUserAndFriend(keystone, user.id, friend.id);
// Everything should now be connected
expect(User.friends.map(({ id }) => id.toString())).toEqual([Friend.id.toString()]);
})
);

test(
'With create',
runner(setupKeystone, async ({ keystone }) => {
const { users } = await createInitialData(keystone);
let user = users[0];
const friendName = sampleOne(alphanumGenerator);
const { data, errors } = await graphqlRequest({
keystone,
query: `
mutation {
updateUser(
id: "${user.id}",
data: { friends: { create: [{ name: "${friendName}" }] } }
) { id friends { id name } }
}
`,
});
expect(errors).toBe(undefined);

const { User, Friend } = await getUserAndFriend(
keystone,
user.id,
data.updateUser.friends[0].id
);

// Everything should now be connected
expect(User.friends.map(({ id }) => id.toString())).toEqual([Friend.id.toString()]);
})
);

test(
'With disconnect',
runner(setupKeystone, async ({ keystone }) => {
// Manually setup a connected Company <-> Location
const { user, friend } = await createUserAndFriend(keystone);

// Run the query to disconnect the location from company
const { data, errors } = await graphqlRequest({
keystone,
query: `
mutation {
updateUser(
id: "${user.id}",
data: { friends: { disconnect: [{ id: "${friend.id}" }] } }
) { id friends { id name } }
}
`,
});
expect(errors).toBe(undefined);
expect(data.updateUser.id).toEqual(user.id);
expect(data.updateUser.friends).toEqual([]);

// Check the link has been broken
const result = await getUserAndFriend(keystone, user.id, friend.id);
expect(result.User.friends).toEqual([]);
})
);

test(
'With disconnectAll',
runner(setupKeystone, async ({ keystone }) => {
// Manually setup a connected Company <-> Location
const { user, friend } = await createUserAndFriend(keystone);

// Run the query to disconnect the location from company
const { data, errors } = await graphqlRequest({
keystone,
query: `
mutation {
updateUser(
id: "${user.id}",
data: { friends: { disconnectAll: true } }
) { id friends { id name } }
}
`,
});
expect(errors).toBe(undefined);
expect(data.updateUser.id).toEqual(user.id);
expect(data.updateUser.friends).toEqual([]);

// Check the link has been broken
const result = await getUserAndFriend(keystone, user.id, friend.id);
expect(result.User.friends).toEqual([]);
})
);
});

describe('Delete', () => {
test(
'delete',
runner(setupKeystone, async ({ keystone }) => {
// Manually setup a connected Company <-> Location
const { user, friend } = await createUserAndFriend(keystone);

// Run the query to disconnect the location from company
const { data, errors } = await graphqlRequest({
keystone,
query: `mutation { deleteUser(id: "${user.id}") { id } } `,
});
expect(errors).toBe(undefined);
expect(data.deleteUser.id).toBe(user.id);

// Check the link has been broken
const result = await getUserAndFriend(keystone, user.id, friend.id);
expect(result.User).toBe(null);
})
);
});
});
})
);
Loading