mutations to mark an item as owned/wanted

This commit is contained in:
Emi Matchu 2020-10-22 20:35:06 -07:00
parent 6c97c15979
commit dd4f34ef73
4 changed files with 2755 additions and 1810 deletions

View file

@ -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

View file

@ -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,
};

View file

@ -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 };