mutations to mark an item as owned/wanted
This commit is contained in:
parent
6c97c15979
commit
dd4f34ef73
4 changed files with 2755 additions and 1810 deletions
|
@ -1,5 +1,12 @@
|
|||
const gql = require("graphql-tag");
|
||||
const { query, getDbCalls, logInAsTestUser } = require("./setup.js");
|
||||
const {
|
||||
query,
|
||||
mutate,
|
||||
getDbCalls,
|
||||
useTestDb,
|
||||
logInAsTestUser,
|
||||
createItem,
|
||||
} = require("./setup.js");
|
||||
|
||||
describe("Item", () => {
|
||||
it("loads metadata", async () => {
|
||||
|
@ -329,32 +336,18 @@ describe("Item", () => {
|
|||
},
|
||||
Object {
|
||||
"currentUserOwnsThis": false,
|
||||
"currentUserWantsThis": true,
|
||||
"currentUserWantsThis": false,
|
||||
"id": "39945",
|
||||
},
|
||||
Object {
|
||||
"currentUserOwnsThis": true,
|
||||
"currentUserOwnsThis": false,
|
||||
"currentUserWantsThis": false,
|
||||
"id": "39948",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(getDbCalls()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"SELECT closet_hangers.*, item_translations.name as item_name FROM closet_hangers
|
||||
INNER JOIN items ON items.id = closet_hangers.item_id
|
||||
INNER JOIN item_translations ON
|
||||
item_translations.item_id = items.id AND locale = \\"en\\"
|
||||
WHERE user_id IN (?)
|
||||
ORDER BY item_name",
|
||||
Array [
|
||||
"44743",
|
||||
],
|
||||
],
|
||||
]
|
||||
`);
|
||||
expect(getDbCalls()).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
|
||||
it("does not own/want items if not logged in", async () => {
|
||||
|
@ -599,4 +592,202 @@ describe("Item", () => {
|
|||
expect(body.canonicalAppearance).toMatchSnapshot("pet layers");
|
||||
expect(getDbCalls()).toMatchSnapshot("db");
|
||||
});
|
||||
|
||||
it("adds new item to items current user owns", async () => {
|
||||
useTestDb();
|
||||
await Promise.all([logInAsTestUser(), createItem("1")]);
|
||||
|
||||
// To start, the user should not own the item yet.
|
||||
let res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
item(id: "1") {
|
||||
currentUserOwnsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserOwnsThis).toBe(false);
|
||||
|
||||
// Mutate the item to mark that the user owns it, and check that the
|
||||
// immediate response reflects this.
|
||||
res = await mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
item: addToItemsCurrentUserOwns(itemId: "1") {
|
||||
currentUserOwnsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserOwnsThis).toBe(true);
|
||||
|
||||
// Confirm that, when replaying the first query, we see that the user now
|
||||
// _does_ own the item.
|
||||
res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
item(id: "1") {
|
||||
currentUserOwnsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserOwnsThis).toBe(true);
|
||||
|
||||
expect(getDbCalls()).toMatchSnapshot("db");
|
||||
});
|
||||
|
||||
it("does not add duplicates when user already owns item", async () => {
|
||||
useTestDb();
|
||||
await Promise.all([logInAsTestUser(), createItem("1")]);
|
||||
|
||||
// Send the add mutation for the first time. This should add it to the
|
||||
// items we own.
|
||||
let res = await mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
item: addToItemsCurrentUserOwns(itemId: "1") {
|
||||
currentUserOwnsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserOwnsThis).toBe(true);
|
||||
|
||||
// Send the add mutation for the second time. This should do nothing,
|
||||
// because we already own it.
|
||||
res = await mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
item: addToItemsCurrentUserOwns(itemId: "1") {
|
||||
currentUserOwnsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserOwnsThis).toBe(true);
|
||||
|
||||
// Afterwards, confirm that it only appears once in the list of items we
|
||||
// own, instead of duplicating.
|
||||
res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
currentUser {
|
||||
itemsTheyOwn {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.currentUser.itemsTheyOwn).toEqual([{ id: "1" }]);
|
||||
|
||||
expect(getDbCalls()).toMatchSnapshot("db");
|
||||
});
|
||||
|
||||
it("adds new item to items current user wants", async () => {
|
||||
useTestDb();
|
||||
await Promise.all([logInAsTestUser(), createItem("1")]);
|
||||
|
||||
// To start, the user should not want the item yet.
|
||||
let res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
item(id: "1") {
|
||||
currentUserWantsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserWantsThis).toBe(false);
|
||||
|
||||
// Mutate the item to mark that the user wants it, and check that the
|
||||
// immediate response reflects this.
|
||||
res = await mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
item: addToItemsCurrentUserWants(itemId: "1") {
|
||||
currentUserWantsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserWantsThis).toBe(true);
|
||||
|
||||
// Confirm that, when replaying the first query, we see that the user now
|
||||
// _does_ want the item.
|
||||
res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
item(id: "1") {
|
||||
currentUserWantsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserWantsThis).toBe(true);
|
||||
|
||||
expect(getDbCalls()).toMatchSnapshot("db");
|
||||
});
|
||||
|
||||
it("does not add duplicates when user already wants item", async () => {
|
||||
useTestDb();
|
||||
await Promise.all([logInAsTestUser(), createItem("1")]);
|
||||
|
||||
// Send the add mutation for the first time. This should add it to the
|
||||
// items we want.
|
||||
let res = await mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
item: addToItemsCurrentUserWants(itemId: "1") {
|
||||
currentUserWantsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserWantsThis).toBe(true);
|
||||
|
||||
// Send the add mutation for the second time. This should do nothing,
|
||||
// because we already want it.
|
||||
res = await mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
item: addToItemsCurrentUserWants(itemId: "1") {
|
||||
currentUserWantsThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.currentUserWantsThis).toBe(true);
|
||||
|
||||
// Afterwards, confirm that it only appears once in the list of items we
|
||||
// want, instead of duplicating.
|
||||
res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
currentUser {
|
||||
itemsTheyWant {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.currentUser.itemsTheyWant).toEqual([{ id: "1" }]);
|
||||
|
||||
expect(getDbCalls()).toMatchSnapshot("db");
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,13 +5,14 @@ const { ApolloServer } = require("apollo-server");
|
|||
const { createTestClient } = require("apollo-server-testing");
|
||||
const { AuthenticationClient } = require("auth0");
|
||||
|
||||
const { getUserIdFromToken } = require("../auth");
|
||||
const connectToDb = require("../db");
|
||||
const actualConnectToDb = jest.requireActual("../db");
|
||||
const { config } = require("../index");
|
||||
|
||||
let accessTokenForQueries = null;
|
||||
|
||||
const { query } = createTestClient(
|
||||
const { query, mutate } = createTestClient(
|
||||
new ApolloServer({
|
||||
...config,
|
||||
context: () =>
|
||||
|
@ -77,7 +78,11 @@ beforeAll(() => {
|
|||
jest.spyOn(global, "Date").mockImplementation(() => NOW);
|
||||
});
|
||||
beforeEach(() => {
|
||||
// Restore auth values to default state.
|
||||
accessTokenForQueries = null;
|
||||
getUserIdFromToken.mockRestore();
|
||||
|
||||
// Restore db values to default state.
|
||||
if (dbExecuteFn) {
|
||||
dbExecuteFn.mockClear();
|
||||
}
|
||||
|
@ -101,20 +106,64 @@ function useTestDb() {
|
|||
dbEnvironment = "test";
|
||||
}
|
||||
|
||||
jest.mock("../auth");
|
||||
async function logInAsTestUser() {
|
||||
const auth0 = new AuthenticationClient({
|
||||
domain: "openneo.us.auth0.com",
|
||||
clientId: process.env.AUTH0_TEST_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_TEST_CLIENT_SECRET,
|
||||
});
|
||||
if (dbEnvironment === "production") {
|
||||
const auth0 = new AuthenticationClient({
|
||||
domain: "openneo.us.auth0.com",
|
||||
clientId: process.env.AUTH0_TEST_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_TEST_CLIENT_SECRET,
|
||||
});
|
||||
|
||||
const res = await auth0.passwordGrant({
|
||||
username: "dti-test",
|
||||
password: process.env.DTI_TEST_USER_PASSWORD,
|
||||
audience: "https://impress-2020.openneo.net/api",
|
||||
});
|
||||
const res = await auth0.passwordGrant({
|
||||
username: "dti-test",
|
||||
password: process.env.DTI_TEST_USER_PASSWORD,
|
||||
audience: "https://impress-2020.openneo.net/api",
|
||||
});
|
||||
|
||||
accessTokenForQueries = res.access_token;
|
||||
accessTokenForQueries = res.access_token;
|
||||
} else if (dbEnvironment === "test") {
|
||||
// Create a test user record. Most of these values don't matter.
|
||||
const db = await connectToDb();
|
||||
await db.query(
|
||||
`INSERT INTO users (id, name, auth_server_id, remote_id)
|
||||
VALUES (1, "test-user-1", 1, 1)`
|
||||
);
|
||||
|
||||
// Mock the server's auth code to return user ID 1.
|
||||
getUserIdFromToken.mockReturnValue("1");
|
||||
accessTokenForQueries = "mock-access-token-test-user-1";
|
||||
} else {
|
||||
throw new Error(`unexpected dbEnvironment ${dbEnvironment}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function createItem(id) {
|
||||
if (dbEnvironment !== "test") {
|
||||
throw new Error(`Please only use createItem in test db!`);
|
||||
}
|
||||
|
||||
const name = `Test Item ${id}`;
|
||||
|
||||
const db = await connectToDb();
|
||||
await Promise.all([
|
||||
db.query(
|
||||
`INSERT INTO items (id, zones_restrict, thumbnail_url, category,
|
||||
type, rarity_index, price, weight_lbs)
|
||||
VALUES (?, "00000000000000000000000000000000000000000000000",
|
||||
"http://example.com/favicon.ico", "Clothes", "Clothes", 101,
|
||||
0, 1);
|
||||
`,
|
||||
[id]
|
||||
),
|
||||
db.query(
|
||||
`INSERT INTO item_translations (item_id, locale, name, description,
|
||||
rarity)
|
||||
VALUES (?, "en", ?, "This is a test item.", "Special")
|
||||
`,
|
||||
[id, name]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
// Add a new `expect(res).toHaveNoErrors()` to call after GraphQL calls!
|
||||
|
@ -141,9 +190,11 @@ process.env["USE_NEW_MODELING"] = "1";
|
|||
|
||||
module.exports = {
|
||||
query,
|
||||
mutate,
|
||||
getDbCalls,
|
||||
clearDbCalls,
|
||||
connectToDb,
|
||||
useTestDb,
|
||||
logInAsTestUser,
|
||||
createItem,
|
||||
};
|
||||
|
|
|
@ -85,6 +85,14 @@ const typeDefs = gql`
|
|||
# bodies like Blue, Green, Red, etc.
|
||||
itemsThatNeedModels(colorId: ID): [Item!]!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
addToItemsCurrentUserOwns(itemId: ID!): Item
|
||||
removeFromItemsCurrentUserOwns(itemId: ID!): Item
|
||||
|
||||
addToItemsCurrentUserWants(itemId: ID!): Item
|
||||
removeFromItemsCurrentUserWants(itemId: ID!): Item
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
@ -273,6 +281,91 @@ const resolvers = {
|
|||
return Array.from(itemIds, (id) => ({ id }));
|
||||
},
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
addToItemsCurrentUserOwns: async (
|
||||
_,
|
||||
{ itemId },
|
||||
{ currentUserId, db, itemLoader }
|
||||
) => {
|
||||
if (currentUserId == null) {
|
||||
throw new Error(`must be logged in`);
|
||||
}
|
||||
|
||||
const item = await itemLoader.load(itemId);
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Send an INSERT query that will add a hanger, if the user doesn't
|
||||
// already have one for this item.
|
||||
// Adapted from https://stackoverflow.com/a/3025332/107415
|
||||
const now = new Date().toISOString().slice(0, 19).replace("T", " ");
|
||||
await db.query(
|
||||
`
|
||||
INSERT INTO closet_hangers
|
||||
(item_id, user_id, quantity, created_at, updated_at, owned)
|
||||
SELECT ?, ?, ?, ?, ?, ? FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM closet_hangers
|
||||
WHERE item_id = ? AND user_id = ? AND owned = ?
|
||||
)
|
||||
`,
|
||||
[itemId, currentUserId, 1, now, now, true, itemId, currentUserId, true]
|
||||
);
|
||||
|
||||
return { id: itemId };
|
||||
},
|
||||
removeFromItemsCurrentUserOwns: () => {
|
||||
throw new Error("TODO: Not yet implemented");
|
||||
},
|
||||
addToItemsCurrentUserWants: async (
|
||||
_,
|
||||
{ itemId },
|
||||
{ currentUserId, db, itemLoader }
|
||||
) => {
|
||||
if (currentUserId == null) {
|
||||
throw new Error(`must be logged in`);
|
||||
}
|
||||
|
||||
const item = await itemLoader.load(itemId);
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Send an INSERT query that will add a hanger, if the user doesn't
|
||||
// already have one for this item.
|
||||
// Adapted from https://stackoverflow.com/a/3025332/107415
|
||||
const now = new Date().toISOString().slice(0, 19).replace("T", " ");
|
||||
await db.query(
|
||||
`
|
||||
INSERT INTO closet_hangers
|
||||
(item_id, user_id, quantity, created_at, updated_at, owned)
|
||||
SELECT ?, ?, ?, ?, ?, ? FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM closet_hangers
|
||||
WHERE item_id = ? AND user_id = ? AND owned = ?
|
||||
)
|
||||
`,
|
||||
[
|
||||
itemId,
|
||||
currentUserId,
|
||||
1,
|
||||
now,
|
||||
now,
|
||||
false,
|
||||
itemId,
|
||||
currentUserId,
|
||||
false,
|
||||
]
|
||||
);
|
||||
|
||||
return { id: itemId };
|
||||
},
|
||||
removeFromItemsCurrentUserWants: () => {
|
||||
throw new Error("TODO: Not yet implemented");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { typeDefs, resolvers };
|
||||
|
|
Loading…
Reference in a new issue