mirror of
https://github.com/outline/outline.git
synced 2026-05-25 05:09:20 -05:00
Convert WebhookSubscription mutations (#8161)
* Convert 'WebhookSubscription' mutations * add tests * remove unnecessary assignment
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
import {
|
||||
buildAdmin,
|
||||
buildUser,
|
||||
buildWebhookSubscription,
|
||||
} from "@server/test/factories";
|
||||
import { getTestServer } from "@server/test/support";
|
||||
|
||||
const server = getTestServer();
|
||||
|
||||
describe("#webhookSubscriptions.list", () => {
|
||||
it("should fail with status 401 unauthorized when user token is missing", async () => {
|
||||
const res = await server.post("/api/webhookSubscriptions.list", {
|
||||
body: {},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
expect(body.message).toEqual("Authentication required");
|
||||
});
|
||||
|
||||
it("should fail with status 403 forbidden for non-admin user", async () => {
|
||||
const user = await buildUser();
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.list", {
|
||||
body: { token: user.getJwtToken() },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(403);
|
||||
expect(body.message).toEqual("Admin role required");
|
||||
});
|
||||
|
||||
it("should return the webhook subscriptions for the user's team", async () => {
|
||||
const user = await buildAdmin();
|
||||
const webhookSubscriptions = await Promise.all(
|
||||
Array(20)
|
||||
.fill(1)
|
||||
.map(() =>
|
||||
buildWebhookSubscription({
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.list", {
|
||||
body: { token: user.getJwtToken() },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.length).toEqual(webhookSubscriptions.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#webhookSubscriptions.create", () => {
|
||||
it("should fail with status 401 unauthorized when user token is missing", async () => {
|
||||
const res = await server.post("/api/webhookSubscriptions.create", {
|
||||
body: {},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
expect(body.message).toEqual("Authentication required");
|
||||
});
|
||||
|
||||
it("should fail with status 403 forbidden for non-admin user", async () => {
|
||||
const user = await buildUser();
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.create", {
|
||||
body: { token: user.getJwtToken() },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(403);
|
||||
expect(body.message).toEqual("Admin role required");
|
||||
});
|
||||
|
||||
it("should successfully create a webhook subscription", async () => {
|
||||
const user = await buildAdmin();
|
||||
const name = "Test webhook";
|
||||
const url = "https://www.example.com";
|
||||
const events = ["comments"];
|
||||
const secret = "Test secret";
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.create", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
name,
|
||||
url,
|
||||
events,
|
||||
secret,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
const webhook = body.data;
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(webhook.name).toEqual(name);
|
||||
expect(webhook.url).toEqual(url);
|
||||
expect(webhook.events).toEqual(events);
|
||||
expect(webhook.secret).toEqual(secret);
|
||||
expect(webhook.enabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#webhookSubscriptions.update", () => {
|
||||
it("should fail with status 401 unauthorized when user token is missing", async () => {
|
||||
const res = await server.post("/api/webhookSubscriptions.update", {
|
||||
body: {},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
expect(body.message).toEqual("Authentication required");
|
||||
});
|
||||
|
||||
it("should fail with status 403 forbidden for non-admin user", async () => {
|
||||
const user = await buildUser();
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.update", {
|
||||
body: { token: user.getJwtToken() },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(403);
|
||||
expect(body.message).toEqual("Admin role required");
|
||||
});
|
||||
|
||||
it("should successfully update a webhook subscription", async () => {
|
||||
const user = await buildAdmin();
|
||||
const name = "Updated webhook name";
|
||||
const url = "https://www.example.com/update";
|
||||
const events = ["comments"];
|
||||
|
||||
const existingWebhook = await buildWebhookSubscription({
|
||||
name: "Created webhook name",
|
||||
url: "https://www.example.com/create",
|
||||
events: ["*"],
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: existingWebhook.id,
|
||||
name,
|
||||
url,
|
||||
events,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
const webhook = body.data;
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(webhook.name).toEqual(name);
|
||||
expect(webhook.url).toEqual(url);
|
||||
expect(webhook.events).toEqual(events);
|
||||
expect(webhook.enabled).toEqual(true);
|
||||
});
|
||||
|
||||
it("should activate a disabled webhook subscription when it's updated", async () => {
|
||||
const user = await buildAdmin();
|
||||
const name = "Updated webhook name";
|
||||
const url = "https://www.example.com/update";
|
||||
const events = ["comments"];
|
||||
|
||||
const disabledWebhook = await buildWebhookSubscription({
|
||||
name: "Created webhook name",
|
||||
url: "https://www.example.com/create",
|
||||
events: ["*"],
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: disabledWebhook.id,
|
||||
name,
|
||||
url,
|
||||
events,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
const webhook = body.data;
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(webhook.name).toEqual(name);
|
||||
expect(webhook.url).toEqual(url);
|
||||
expect(webhook.events).toEqual(events);
|
||||
expect(webhook.enabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#webhookSubscriptions.delete", () => {
|
||||
it("should fail with status 401 unauthorized when user token is missing", async () => {
|
||||
const res = await server.post("/api/webhookSubscriptions.delete", {
|
||||
body: {},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
expect(body.message).toEqual("Authentication required");
|
||||
});
|
||||
|
||||
it("should fail with status 403 forbidden for non-admin user", async () => {
|
||||
const user = await buildUser();
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.delete", {
|
||||
body: { token: user.getJwtToken() },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(403);
|
||||
expect(body.message).toEqual("Admin role required");
|
||||
});
|
||||
|
||||
it("should successfully delete a webhook subscription", async () => {
|
||||
const user = await buildAdmin();
|
||||
const createdWebhook = await buildWebhookSubscription({
|
||||
name: "Test webhook",
|
||||
url: "https://www.example.com",
|
||||
events: ["*"],
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
const res = await server.post("/api/webhookSubscriptions.delete", {
|
||||
body: { token: user.getJwtToken(), id: createdWebhook.id },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.success).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import { UserRole } from "@shared/types";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { transaction } from "@server/middlewares/transaction";
|
||||
import validate from "@server/middlewares/validate";
|
||||
import { WebhookSubscription, Event } from "@server/models";
|
||||
import { WebhookSubscription } from "@server/models";
|
||||
import { authorize } from "@server/policies";
|
||||
import pagination from "@server/routes/api/middlewares/pagination";
|
||||
import { APIContext } from "@server/types";
|
||||
@@ -20,7 +20,9 @@ router.post(
|
||||
pagination(),
|
||||
async (ctx: APIContext) => {
|
||||
const { user } = ctx.state.auth;
|
||||
|
||||
authorize(user, "listWebhookSubscription", user.team);
|
||||
|
||||
const webhooks = await WebhookSubscription.findAll({
|
||||
where: {
|
||||
teamId: user.teamId,
|
||||
@@ -43,34 +45,19 @@ router.post(
|
||||
validate(T.WebhookSubscriptionsCreateSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.WebhookSubscriptionsCreateReq>) => {
|
||||
const { transaction } = ctx.state;
|
||||
const { name, url, secret, events } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
|
||||
authorize(user, "createWebhookSubscription", user.team);
|
||||
|
||||
const { name, url, secret } = ctx.input.body;
|
||||
const events: string[] = compact(ctx.input.body.events);
|
||||
|
||||
const webhookSubscription = await WebhookSubscription.create(
|
||||
{
|
||||
name,
|
||||
events,
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
url,
|
||||
enabled: true,
|
||||
secret: isEmpty(secret) ? undefined : secret,
|
||||
},
|
||||
{ transaction }
|
||||
);
|
||||
|
||||
await Event.createFromContext(ctx, {
|
||||
name: "webhookSubscriptions.create",
|
||||
modelId: webhookSubscription.id,
|
||||
data: {
|
||||
name,
|
||||
url,
|
||||
events,
|
||||
},
|
||||
const webhookSubscription = await WebhookSubscription.createWithCtx(ctx, {
|
||||
name,
|
||||
url,
|
||||
events: compact(events),
|
||||
enabled: true,
|
||||
secret: isEmpty(secret) ? undefined : secret,
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
@@ -88,6 +75,7 @@ router.post(
|
||||
const { id } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
const { transaction } = ctx.state;
|
||||
|
||||
const webhookSubscription = await WebhookSubscription.findByPk(id, {
|
||||
rejectOnEmpty: true,
|
||||
lock: transaction.LOCK.UPDATE,
|
||||
@@ -96,17 +84,7 @@ router.post(
|
||||
|
||||
authorize(user, "delete", webhookSubscription);
|
||||
|
||||
await webhookSubscription.destroy({ transaction });
|
||||
|
||||
await Event.createFromContext(ctx, {
|
||||
name: "webhookSubscriptions.delete",
|
||||
modelId: webhookSubscription.id,
|
||||
data: {
|
||||
name: webhookSubscription.name,
|
||||
url: webhookSubscription.url,
|
||||
events: webhookSubscription.events,
|
||||
},
|
||||
});
|
||||
await webhookSubscription.destroyWithCtx(ctx);
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
@@ -120,10 +98,10 @@ router.post(
|
||||
validate(T.WebhookSubscriptionsUpdateSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.WebhookSubscriptionsUpdateReq>) => {
|
||||
const { id, name, url, secret } = ctx.input.body;
|
||||
const { id, name, url, secret, events } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
const { transaction } = ctx.state;
|
||||
const events: string[] = compact(ctx.input.body.events);
|
||||
|
||||
const webhookSubscription = await WebhookSubscription.findByPk(id, {
|
||||
rejectOnEmpty: true,
|
||||
lock: transaction.LOCK.UPDATE,
|
||||
@@ -132,25 +110,12 @@ router.post(
|
||||
|
||||
authorize(user, "update", webhookSubscription);
|
||||
|
||||
await webhookSubscription.update(
|
||||
{
|
||||
name,
|
||||
url,
|
||||
events,
|
||||
enabled: true,
|
||||
secret: isEmpty(secret) ? undefined : secret,
|
||||
},
|
||||
{ transaction }
|
||||
);
|
||||
|
||||
await Event.createFromContext(ctx, {
|
||||
name: "webhookSubscriptions.update",
|
||||
modelId: webhookSubscription.id,
|
||||
data: {
|
||||
name: webhookSubscription.name,
|
||||
url: webhookSubscription.url,
|
||||
events: webhookSubscription.events,
|
||||
},
|
||||
await webhookSubscription.updateWithCtx(ctx, {
|
||||
name,
|
||||
url,
|
||||
events: compact(events),
|
||||
enabled: true,
|
||||
secret: isEmpty(secret) ? undefined : secret,
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
|
||||
@@ -44,6 +44,8 @@ class WebhookSubscription extends ParanoidModel<
|
||||
InferAttributes<WebhookSubscription>,
|
||||
Partial<InferCreationAttributes<WebhookSubscription>>
|
||||
> {
|
||||
static eventNamespace = "webhookSubscriptions";
|
||||
|
||||
@NotEmpty
|
||||
@Length({ max: 255, msg: "Webhook name be less than 255 characters" })
|
||||
@Column
|
||||
|
||||
Reference in New Issue
Block a user