mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 02:15:01 +00:00
Test server, including GraphQL endpoint
This commit is contained in:
parent
4bdb243933
commit
50fde5c4d3
7 changed files with 563 additions and 7 deletions
|
|
@ -9,8 +9,8 @@ module.exports = {
|
|||
// TODO: Add these as we can...
|
||||
"/packages/webamp/",
|
||||
// TODO: Fix config improt so that this can work.
|
||||
"/packages/skin-database/",
|
||||
"/packages/webamp-modern/src/__tests__/integration*",
|
||||
],
|
||||
testEnvironment: "jsdom",
|
||||
setupFiles: ["<rootDir>/packages/skin-database/jest-setup.js"],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query.fetch_skin_by_md5 (debug data) 1`] = `
|
||||
Object {
|
||||
"fetch_skin_by_md5": Object {
|
||||
"archive_files": Array [
|
||||
Object {
|
||||
"date": "1995-12-17T11:24:00.000Z",
|
||||
"file_md5": "a_fake_file_md5",
|
||||
"filename": null,
|
||||
"is_directory": false,
|
||||
"size": null,
|
||||
"skin": Object {
|
||||
"md5": "a_fake_md5",
|
||||
},
|
||||
"text_content": null,
|
||||
"url": "https://zip-worker.jordan1320.workers.dev/zip/a_fake_md5/null",
|
||||
},
|
||||
],
|
||||
"average_color": null,
|
||||
"download_url": "https://cdn.webampskins.org/skins/a_fake_md5.wsz",
|
||||
"filename": "path.wsz",
|
||||
"id": 50,
|
||||
"internet_archive_item": Object {
|
||||
"identifier": "a_fake_ia_identifier",
|
||||
"metadata_url": "https://archive.org/metadata/a_fake_ia_identifier",
|
||||
"raw_metadata_json": null,
|
||||
"skin": Object {
|
||||
"md5": "a_fake_md5",
|
||||
},
|
||||
"url": "https://archive.org/details/a_fake_ia_identifier",
|
||||
},
|
||||
"md5": "a_fake_md5",
|
||||
"museum_url": "https://skins.webamp.org/skin/a_fake_md5",
|
||||
"nsfw": false,
|
||||
"readme_text": null,
|
||||
"reviews": Array [],
|
||||
"screenshot_url": "https://cdn.webampskins.org/screenshots/a_fake_md5.png",
|
||||
"tweeted": false,
|
||||
"tweets": Array [],
|
||||
"webamp_url": "https://webamp.org?skinUrl=https://cdn.webampskins.org/skins/a_fake_md5.wsz",
|
||||
},
|
||||
}
|
||||
`;
|
||||
484
packages/skin-database/api/__tests__/graphql.test.ts
Normal file
484
packages/skin-database/api/__tests__/graphql.test.ts
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
import { Application } from "express";
|
||||
import { knex } from "../../db";
|
||||
import request from "supertest"; // supertest is a framework that allows to easily test web apis
|
||||
import { createApp } from "../app";
|
||||
import SkinModel from "../../data/SkinModel";
|
||||
import * as S3 from "../../s3";
|
||||
import * as Auth from "../auth";
|
||||
import { processUserUploads } from "../processUserUploads";
|
||||
import UserContext from "../../data/UserContext";
|
||||
import { searchIndex } from "../../algolia";
|
||||
jest.mock("../../s3");
|
||||
jest.mock("../../algolia");
|
||||
jest.mock("../processUserUploads");
|
||||
jest.mock("../auth");
|
||||
|
||||
let app: Application;
|
||||
const handler = jest.fn();
|
||||
const log = jest.fn();
|
||||
const logError = jest.fn();
|
||||
|
||||
let username: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
username = "<MOCKED>";
|
||||
app = createApp({
|
||||
eventHandler: handler,
|
||||
extraMiddleware: (req, res, next) => {
|
||||
req.session.username = username;
|
||||
next();
|
||||
},
|
||||
logger: { log, logError },
|
||||
});
|
||||
await knex.migrate.latest();
|
||||
await knex.seed.run();
|
||||
});
|
||||
|
||||
function gql(templateString: TemplateStringsArray): string {
|
||||
return templateString[0];
|
||||
}
|
||||
|
||||
async function graphQLRequest(query: string, variables?: any) {
|
||||
const { body } = await request(app)
|
||||
.post("/graphql")
|
||||
.send({ query, variables: variables ?? {} });
|
||||
if (body.errors && body.errors.length) {
|
||||
console.warn(body);
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
describe(".me", () => {
|
||||
test("logged in ", async () => {
|
||||
const { data } = await graphQLRequest(gql`
|
||||
query {
|
||||
me {
|
||||
username
|
||||
}
|
||||
}
|
||||
`);
|
||||
expect(data).toEqual({ me: { username: "<MOCKED>" } });
|
||||
});
|
||||
test("not logged in", async () => {
|
||||
username = undefined;
|
||||
const { data } = await graphQLRequest(gql`
|
||||
query {
|
||||
me {
|
||||
username
|
||||
}
|
||||
}
|
||||
`);
|
||||
expect(data).toEqual({ me: { username: null } });
|
||||
});
|
||||
});
|
||||
|
||||
test("/auth", async () => {
|
||||
const { body } = await request(app)
|
||||
.get("/auth")
|
||||
.expect(302)
|
||||
.expect(
|
||||
"Location",
|
||||
"https://discord.com/api/oauth2/authorize?client_id=%3CDUMMY_DISCORD_CLIENT_ID%3E&redirect_uri=https%3A%2F%2Fapi.webampskins.org%2Fauth%2Fdiscord&response_type=code&scope=identify%20guilds"
|
||||
);
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
describe("/auth/discord", () => {
|
||||
test("valid code", async () => {
|
||||
const response = await request(app)
|
||||
.get("/auth/discord")
|
||||
.query({ code: "<A_FAKE_CODE>" })
|
||||
.expect(302)
|
||||
.expect("Location", "https://skins.webamp.org/review/");
|
||||
// TODO: Assert that we get cookie headers. I think that will not work now
|
||||
// because express does not think it's secure in a test env.
|
||||
expect(Auth.auth).toHaveBeenCalledWith("<A_FAKE_CODE>");
|
||||
expect(response.body).toEqual({});
|
||||
});
|
||||
test("missing code", async () => {
|
||||
const { body } = await request(app).get("/auth/discord").expect(400);
|
||||
expect(Auth.auth).not.toHaveBeenCalled();
|
||||
expect(body).toEqual({ message: "Expected to get a code" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Query.skins", () => {
|
||||
test("no query params", async () => {
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
query {
|
||||
skins(sort: MUSEUM) {
|
||||
count
|
||||
nodes {
|
||||
md5
|
||||
filename
|
||||
nsfw
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
expect(data.skins).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"count": 6,
|
||||
"nodes": Array [
|
||||
Object {
|
||||
"filename": "Zelda_Amp_3.wsz",
|
||||
"md5": "48bbdbbeb03d347e59b1eebda4d352d0",
|
||||
"nsfw": false,
|
||||
},
|
||||
Object {
|
||||
"filename": "path.wsz",
|
||||
"md5": "a_fake_md5",
|
||||
"nsfw": false,
|
||||
},
|
||||
Object {
|
||||
"filename": "tweeted.wsz",
|
||||
"md5": "a_tweeted_md5",
|
||||
"nsfw": false,
|
||||
},
|
||||
Object {
|
||||
"filename": "approved.wsz",
|
||||
"md5": "an_approved_md5",
|
||||
"nsfw": false,
|
||||
},
|
||||
Object {
|
||||
"filename": "rejected.wsz",
|
||||
"md5": "a_rejected_md5",
|
||||
"nsfw": false,
|
||||
},
|
||||
Object {
|
||||
"filename": "nsfw.wsz",
|
||||
"md5": "a_nsfw_md5",
|
||||
"nsfw": true,
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
test("first and offset", async () => {
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
query MyQuery($first: Int, $offset: Int) {
|
||||
skins(first: $first, offset: $offset, sort: MUSEUM) {
|
||||
count
|
||||
nodes {
|
||||
md5
|
||||
filename
|
||||
nsfw
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ first: 2, offset: 1 }
|
||||
);
|
||||
expect(data.skins).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"count": 6,
|
||||
"nodes": Array [
|
||||
Object {
|
||||
"filename": "path.wsz",
|
||||
"md5": "a_fake_md5",
|
||||
"nsfw": false,
|
||||
},
|
||||
Object {
|
||||
"filename": "tweeted.wsz",
|
||||
"md5": "a_tweeted_md5",
|
||||
"nsfw": false,
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
test("Query.fetch_skin_by_md5 (debug data)", async () => {
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
query MyQuery($md5: String!) {
|
||||
fetch_skin_by_md5(md5: $md5) {
|
||||
id
|
||||
md5
|
||||
museum_url
|
||||
webamp_url
|
||||
screenshot_url
|
||||
download_url
|
||||
filename
|
||||
readme_text
|
||||
nsfw
|
||||
average_color
|
||||
tweeted
|
||||
tweets {
|
||||
url
|
||||
}
|
||||
archive_files {
|
||||
filename
|
||||
url
|
||||
date
|
||||
file_md5
|
||||
size
|
||||
text_content
|
||||
is_directory
|
||||
skin {
|
||||
md5
|
||||
}
|
||||
}
|
||||
filename
|
||||
internet_archive_item {
|
||||
identifier
|
||||
url
|
||||
metadata_url
|
||||
raw_metadata_json
|
||||
skin {
|
||||
md5
|
||||
}
|
||||
}
|
||||
reviews {
|
||||
skin {
|
||||
md5
|
||||
}
|
||||
reviewer
|
||||
rating
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ md5: "a_fake_md5" }
|
||||
);
|
||||
expect(data).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("Mutation.request_nsfw_review_for_skin", async () => {
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
mutation ($md5: String!) {
|
||||
request_nsfw_review_for_skin(md5: $md5)
|
||||
}
|
||||
`,
|
||||
{ md5: "a_fake_md5" }
|
||||
);
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
type: "REVIEW_REQUESTED",
|
||||
md5: "a_fake_md5",
|
||||
});
|
||||
expect(data).toEqual({ request_nsfw_review_for_skin: true });
|
||||
});
|
||||
|
||||
test("Mutation.approve_skin", async () => {
|
||||
const ctx = new UserContext();
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
mutation ($md5: String!) {
|
||||
approve_skin(md5: $md5)
|
||||
}
|
||||
`,
|
||||
{ md5: "a_fake_md5" }
|
||||
);
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
type: "APPROVED_SKIN",
|
||||
md5: "a_fake_md5",
|
||||
});
|
||||
expect(data).toEqual({ approve_skin: true });
|
||||
const skin = await SkinModel.fromMd5(ctx, "a_fake_md5");
|
||||
|
||||
expect(await skin?.getTweetStatus()).toEqual("APPROVED");
|
||||
});
|
||||
|
||||
test("Mutation.reject_skin", async () => {
|
||||
const ctx = new UserContext();
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
mutation ($md5: String!) {
|
||||
reject_skin(md5: $md5)
|
||||
}
|
||||
`,
|
||||
{ md5: "a_fake_md5" }
|
||||
);
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
type: "REJECTED_SKIN",
|
||||
md5: "a_fake_md5",
|
||||
});
|
||||
expect(data).toEqual({ reject_skin: true });
|
||||
const skin = await SkinModel.fromMd5(ctx, "a_fake_md5");
|
||||
|
||||
expect(await skin?.getTweetStatus()).toEqual("REJECTED");
|
||||
});
|
||||
|
||||
test("Mutation.mark_skin_nsfw", async () => {
|
||||
const ctx = new UserContext();
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
mutation ($md5: String!) {
|
||||
mark_skin_nsfw(md5: $md5)
|
||||
}
|
||||
`,
|
||||
{ md5: "a_fake_md5" }
|
||||
);
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
type: "MARKED_SKIN_NSFW",
|
||||
md5: "a_fake_md5",
|
||||
});
|
||||
expect(searchIndex.partialUpdateObjects).toHaveBeenCalledWith([
|
||||
{ nsfw: true, objectID: "a_fake_md5" },
|
||||
]);
|
||||
expect(data).toEqual({ mark_skin_nsfw: true });
|
||||
const skin = await SkinModel.fromMd5(ctx, "a_fake_md5");
|
||||
|
||||
expect(await skin?.getTweetStatus()).toEqual("NSFW");
|
||||
});
|
||||
|
||||
describe("Query.skin_to_review", () => {
|
||||
test("logged in ", async () => {
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
query {
|
||||
skin_to_review {
|
||||
md5
|
||||
filename
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
expect(data).toEqual({
|
||||
skin_to_review: {
|
||||
filename: expect.any(String),
|
||||
md5: expect.any(String),
|
||||
},
|
||||
});
|
||||
});
|
||||
test("not logged in ", async () => {
|
||||
username = undefined;
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
query {
|
||||
skin_to_review {
|
||||
md5
|
||||
filename
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
expect(data).toEqual({ skin_to_review: null });
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Actually upload some skins?
|
||||
test("Query.upload_statuses_by_md5", async () => {
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
query ($md5s: [String!]!) {
|
||||
upload_statuses_by_md5(md5s: $md5s) {
|
||||
id
|
||||
status
|
||||
upload_md5
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ md5s: ["a_fake_md5", "a_missing_md5"] }
|
||||
);
|
||||
expect(data.upload_statuses_by_md5).toEqual([]);
|
||||
});
|
||||
|
||||
test("Mutation.upload.get_upload_urls", async () => {
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
mutation ($files: [UploadUrlRequest!]!) {
|
||||
upload {
|
||||
get_upload_urls(files: $files) {
|
||||
id
|
||||
url
|
||||
md5
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
files: [
|
||||
{
|
||||
md5: "3b73bcd43c30b85d4cad3083e8ac9695",
|
||||
filename: "a_fake_new_file.wsz",
|
||||
},
|
||||
{
|
||||
md5: "48bbdbbeb03d347e59b1eebda4d352d0",
|
||||
filename: "a_new_name_for_a_file_that_exists.wsz",
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
expect(S3.getSkinUploadUrl).toHaveBeenCalledWith(
|
||||
"3b73bcd43c30b85d4cad3083e8ac9695",
|
||||
expect.any(Number)
|
||||
);
|
||||
|
||||
expect(data.upload.get_upload_urls).toEqual([
|
||||
{
|
||||
md5: "3b73bcd43c30b85d4cad3083e8ac9695",
|
||||
id: expect.any(String),
|
||||
url: "<MOCK_S3_UPLOAD_URL>",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("An Upload Flow", async () => {
|
||||
// Request an upload URL
|
||||
const md5 = "3b73bcd43c30b85d4cad3083e8ac9695";
|
||||
const filename = "a_fake_new_file.wsz";
|
||||
const { data } = await graphQLRequest(
|
||||
gql`
|
||||
mutation ($files: [UploadUrlRequest!]!) {
|
||||
upload {
|
||||
get_upload_urls(files: $files) {
|
||||
id
|
||||
url
|
||||
md5
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ files: [{ md5, filename }] }
|
||||
);
|
||||
|
||||
expect(data.upload.get_upload_urls).toEqual([
|
||||
{
|
||||
md5,
|
||||
id: expect.any(String),
|
||||
url: "<MOCK_S3_UPLOAD_URL>",
|
||||
},
|
||||
]);
|
||||
|
||||
const id = data.upload.get_upload_urls[0].id;
|
||||
|
||||
const requestedUpload = await knex("skin_uploads").where({ id }).first();
|
||||
expect(requestedUpload).toEqual({
|
||||
filename,
|
||||
id: Number(id),
|
||||
skin_md5: md5,
|
||||
status: "URL_REQUESTED",
|
||||
});
|
||||
|
||||
// Report that we've uploaded the skin to S3 (we lie)
|
||||
const { data: uploadReportData } = await graphQLRequest(
|
||||
gql`
|
||||
mutation ($id: String!, $md5: String!) {
|
||||
upload {
|
||||
report_skin_uploaded(id: $id, md5: $md5)
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ md5, id }
|
||||
);
|
||||
expect(uploadReportData.upload.report_skin_uploaded).toEqual(true);
|
||||
expect(processUserUploads).toHaveBeenCalled();
|
||||
|
||||
const reportedUpload = await knex("skin_uploads").where({ id }).first();
|
||||
expect(reportedUpload).toEqual({
|
||||
filename,
|
||||
id: Number(id),
|
||||
skin_md5: md5,
|
||||
status: "UPLOAD_REPORTED",
|
||||
});
|
||||
});
|
||||
|
|
@ -53,7 +53,7 @@ test("/auth", async () => {
|
|||
.expect(302)
|
||||
.expect(
|
||||
"Location",
|
||||
"https://discord.com/api/oauth2/authorize?client_id=560264562222432304&redirect_uri=https%3A%2F%2Fapi.webampskins.org%2Fauth%2Fdiscord&response_type=code&scope=identify%20guilds"
|
||||
"https://discord.com/api/oauth2/authorize?client_id=%3CDUMMY_DISCORD_CLIENT_ID%3E&redirect_uri=https%3A%2F%2Fapi.webampskins.org%2Fauth%2Fdiscord&response_type=code&scope=identify%20guilds"
|
||||
);
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ export function createApp({ eventHandler, extraMiddleware, logger }: Options) {
|
|||
name: "session",
|
||||
secret: SECRET,
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
||||
// @ts-ignore Tests fail if this is missing, but prod is fine.
|
||||
keys: "what",
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
27
packages/skin-database/jest-setup.js
Normal file
27
packages/skin-database/jest-setup.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// In the real app, these are set via .env
|
||||
process.env.LOCAL_FILE_CACHE = "";
|
||||
process.env.CLOUDFLARE_PURGE_AUTH_KEY = "";
|
||||
process.env.CAPTBARITONE_USER_ID = "";
|
||||
process.env.TEST_CHANNEL_ID = "";
|
||||
process.env.TWEET_BOT_CHANNEL_ID = "";
|
||||
process.env.SKIN_UPLOADS_CHANNEL_ID = "";
|
||||
process.env.SKIN_REVIEW_CHANNEL_ID = "";
|
||||
process.env.NSFW_SKIN_CHANNEL_ID = "";
|
||||
process.env.FEEDBACK_SKIN_CHANNEL_ID = "";
|
||||
process.env.POPULAR_TWEETS_CHANNEL_ID = "";
|
||||
process.env.DISCORD_TOKEN = "";
|
||||
process.env.DISCORD_WEBAMP_SERVER_ID = "<DUMMY_DISCORD_WEBAMP_SERVER_ID>";
|
||||
process.env.DISCORD_CLIENT_ID = "<DUMMY_DISCORD_CLIENT_ID>";
|
||||
process.env.DISCORD_CLIENT_SECRET = "<DUMMY_DISCORD_CLIENT_SECRET>";
|
||||
process.env.DISCORD_REDIRECT_URL = "https://api.webampskins.org/auth/discord";
|
||||
process.env.LOGIN_REDIRECT_URL = "https://skins.webamp.org/review/";
|
||||
process.env.ALGOLIA_ACCOUNT = "";
|
||||
process.env.ALGOLIA_INDEX = "";
|
||||
process.env.ALGOLIA_KEY = "";
|
||||
process.env.TWITTER_API_KEY = "";
|
||||
process.env.TWITTER_API_SECRET = "";
|
||||
process.env.TWITTER_ACCESS_TOKEN = "";
|
||||
process.env.TWITTER_ACCESS_TOKEN_SECRET = "";
|
||||
process.env.INSTAGRAM_ACCESS_TOKEN = "";
|
||||
process.env.INSTAGRAM_ACCOUNT_ID = "";
|
||||
process.env.SECRET = "";
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
import path from "path";
|
||||
import { PROJECT_ROOT } from "./config";
|
||||
|
||||
const production = {
|
||||
client: "sqlite3",
|
||||
connection: {
|
||||
filename: path.join(PROJECT_ROOT, "./skins.sqlite3"),
|
||||
filename: path.join(__dirname, "./skins.sqlite3"),
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
debug: false,
|
||||
migrations: {
|
||||
tableName: "knex_migrations",
|
||||
directory: path.join(PROJECT_ROOT, "./migrations"),
|
||||
directory: path.join(__dirname, "./migrations"),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -20,14 +19,14 @@ const configs = {
|
|||
connection: ":memory:",
|
||||
seeds: {
|
||||
// Only put this in the test config so that we ensure we never clobber prod data.
|
||||
directory: path.join(PROJECT_ROOT, "./seeds"),
|
||||
directory: path.join(__dirname, "./seeds"),
|
||||
},
|
||||
},
|
||||
development: {
|
||||
...production,
|
||||
connection: {
|
||||
...production.connection,
|
||||
filename: path.join(PROJECT_ROOT, "./skins-dev.sqlite3"),
|
||||
filename: path.join(__dirname, "./skins-dev.sqlite3"),
|
||||
},
|
||||
},
|
||||
production,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue