iceshrimp/packages/backend/test/utils.ts
2023-06-05 16:40:48 -07:00

404 lines
7.8 KiB
TypeScript

import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
import * as childProcess from "child_process";
import * as http from "node:http";
import { SIGKILL } from "constants";
import WebSocket from "ws";
import * as misskey from "calckey-js";
import fetch from "node-fetch";
import FormData from "form-data";
import { DataSource } from "typeorm";
import loadConfig from "../src/config/load.js";
import { entities } from "../src/db/postgre.js";
import got from "got";
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const config = loadConfig();
export const port = config.port;
export const async = (fn: Function) => (done: Function) => {
fn().then(
() => {
done();
},
(err: Error) => {
done(err);
},
);
};
export const api = async (endpoint: string, params: any, me?: any) => {
endpoint = endpoint.replace(/^\//, "");
const auth = me
? {
i: me.token,
}
: {};
const res = await got<string>(`http://localhost:${port}/api/${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Object.assign(auth, params)),
retry: {
limit: 0,
},
hooks: {
beforeError: [
(error) => {
const { response } = error;
if (response?.body) console.warn(response.body);
return error;
},
],
},
});
const status = res.statusCode;
const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
return {
status,
body,
};
};
export const request = async (
endpoint: string,
params: any,
me?: any,
): Promise<{ body: any; status: number }> => {
const auth = me
? {
i: me.token,
}
: {};
const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Object.assign(auth, params)),
});
const status = res.status;
const body = res.status !== 204 ? await res.json().catch() : null;
return {
body,
status,
};
};
export const signup = async (params?: any): Promise<any> => {
const q = Object.assign(
{
username: "test",
password: "test",
},
params,
);
const res = await api("signup", q);
return res.body;
};
export const post = async (
user: any,
params?: misskey.Endpoints["notes/create"]["req"],
): Promise<misskey.entities.Note> => {
const q = Object.assign(
{
text: "test",
},
params,
);
const res = await api("notes/create", q, user);
return res.body ? res.body.createdNote : null;
};
export const react = async (
user: any,
note: any,
reaction: string,
): Promise<any> => {
await api(
"notes/reactions/create",
{
noteId: note.id,
reaction: reaction,
},
user,
);
};
/**
* Upload file
* @param user User
* @param _path Optional, absolute path or relative from ./resources/
*/
export const uploadFile = async (user: any, _path?: string): Promise<any> => {
const absPath =
_path == null
? `${_dirname}/resources/Lenna.jpg`
: path.isAbsolute(_path)
? _path
: `${_dirname}/resources/${_path}`;
const formData = new FormData() as any;
formData.append("i", user.token);
formData.append("file", fs.createReadStream(absPath));
formData.append("force", "true");
const res = await got<string>(
`http://localhost:${port}/api/drive/files/create`,
{
method: "POST",
body: formData,
retry: {
limit: 0,
},
},
);
const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
return body;
};
export const uploadUrl = async (user: any, url: string) => {
let file: any;
const ws = await connectStream(user, "main", (msg) => {
if (msg.type === "driveFileCreated") {
file = msg.body;
}
});
await api(
"drive/files/upload-from-url",
{
url,
force: true,
},
user,
);
await sleep(5000);
ws.close();
return file;
};
export function connectStream(
user: any,
channel: string,
listener: (message: Record<string, any>) => any,
params?: any,
): Promise<WebSocket> {
return new Promise((res, rej) => {
const ws = new WebSocket(
`ws://localhost:${port}/streaming?i=${user.token}`,
);
ws.on("open", () => {
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === "channel" && msg.body.id === "a") {
listener(msg.body);
} else if (msg.type === "connected" && msg.body.id === "a") {
res(ws);
}
});
ws.send(
JSON.stringify({
type: "connect",
body: {
channel: channel,
id: "a",
pong: true,
params: params,
},
}),
);
});
});
}
export const waitFire = async (
user: any,
channel: string,
trgr: () => any,
cond: (msg: Record<string, any>) => boolean,
params?: any,
) => {
return new Promise<boolean>(async (res, rej) => {
let timer: NodeJS.Timeout;
let ws: WebSocket;
try {
ws = await connectStream(
user,
channel,
(msg) => {
if (cond(msg)) {
ws.close();
if (timer) clearTimeout(timer);
res(true);
}
},
params,
);
} catch (e) {
rej(e);
}
if (!ws!) return;
timer = setTimeout(() => {
ws.close();
res(false);
}, 3000);
try {
await trgr();
} catch (e) {
ws.close();
if (timer) clearTimeout(timer);
rej(e);
}
});
};
export const simpleGet = async (
path: string,
accept = "*/*",
): Promise<{ status?: number; type?: string; location?: string }> => {
// node-fetchだと3xxを取れない
return await new Promise((resolve, reject) => {
const req = http.request(
`http://localhost:${port}${path}`,
{
headers: {
Accept: accept,
},
},
(res) => {
if (res.statusCode! >= 400) {
reject(res);
} else {
resolve({
status: res.statusCode,
type: res.headers["content-type"],
location: res.headers.location,
});
}
},
);
req.end();
});
};
export function launchServer(
callbackSpawnedProcess: (p: childProcess.ChildProcess) => void,
moreProcess: () => Promise<void> = async () => {},
) {
return (done: (err?: Error) => any) => {
const p = childProcess.spawn("node", [`${_dirname}/../index.js`], {
stdio: ["inherit", "inherit", "inherit", "ipc"],
env: { NODE_ENV: "test", PATH: process.env.PATH },
});
callbackSpawnedProcess(p);
p.on("message", (message) => {
if (message === "ok")
moreProcess()
.then(() => done())
.catch((e) => done(e));
});
};
}
export async function initTestDb(justBorrow = false, initEntities?: any[]) {
if (process.env.NODE_ENV !== "test") throw "NODE_ENV is not a test";
const db = new DataSource({
type: "postgres",
host: config.db.host,
port: config.db.port,
username: config.db.user,
password: config.db.pass,
database: config.db.db,
synchronize: !justBorrow,
dropSchema: !justBorrow,
entities: initEntities || entities,
});
await db.initialize();
return db;
}
export function startServer(
timeout = 60 * 1000,
): Promise<childProcess.ChildProcess> {
return new Promise((res, rej) => {
const t = setTimeout(() => {
p.kill(SIGKILL);
rej("timeout to start");
}, timeout);
const p = childProcess.spawn("node", [`${_dirname}/../built/index.js`], {
stdio: ["inherit", "inherit", "inherit", "ipc"],
env: { NODE_ENV: "test", PATH: process.env.PATH },
});
p.on("error", (e) => rej(e));
p.on("message", (message) => {
if (message === "ok") {
clearTimeout(t);
res(p);
}
});
});
}
export function shutdownServer(
p: childProcess.ChildProcess,
timeout = 20 * 1000,
) {
return new Promise((res, rej) => {
const t = setTimeout(() => {
p.kill(SIGKILL);
res("force exit");
}, timeout);
p.once("exit", () => {
clearTimeout(t);
res("exited");
});
p.kill();
});
}
export function sleep(msec: number) {
return new Promise<void>((res) => {
setTimeout(() => {
res();
}, msec);
});
}