-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update the underlying data schema used by Keystone
- Loading branch information
Showing
45 changed files
with
4,719 additions
and
646 deletions.
There are no files selected for viewing
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,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
378
api-tests/relationships/crud-self-ref/many-to-many-one-sided.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,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); | ||
}) | ||
); | ||
}); | ||
}); | ||
}) | ||
); |
Oops, something went wrong.