Skip to content

Commit d3f1bec

Browse files
committed
Merge pull request #171 from fedify-dev/fix/allow-formdata-for-statuses
2 parents a623cb7 + 409f809 commit d3f1bec

3 files changed

Lines changed: 446 additions & 264 deletions

File tree

CHANGES.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ Version 0.7.0
66

77
To be released.
88

9+
- Fixed `POST /api/v1/statuses` and `PUT /api/v1/statuses/:id` endpoints
10+
rejecting FormData requests. These endpoints now properly accept both
11+
JSON and FormData content types, improving compatibility with Mastodon
12+
clients that send `multipart/form-data` requests.
13+
[[#170], [#171] by Emelia Smith]
14+
15+
[#170]: https://github.com/fedify-dev/hollo/issues/170
16+
[#171]: https://github.com/fedify-dev/hollo/pull/171
17+
918

1019
Version 0.6.2
1120
-------------

src/api/v1/statuses.test.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { beforeEach, describe, expect, it } from "vitest";
2+
3+
import { cleanDatabase } from "../../../tests/helpers";
4+
import {
5+
bearerAuthorization,
6+
createAccount,
7+
createOAuthApplication,
8+
getAccessToken,
9+
getApplication,
10+
} from "../../../tests/helpers/oauth";
11+
12+
import app from "../../index";
13+
14+
describe.sequential("/api/v1/accounts/verify_credentials", () => {
15+
let client: Awaited<ReturnType<typeof createOAuthApplication>>;
16+
let account: Awaited<ReturnType<typeof createAccount>>;
17+
let application: Awaited<ReturnType<typeof getApplication>>;
18+
let accessToken: Awaited<ReturnType<typeof getAccessToken>>;
19+
20+
beforeEach(async () => {
21+
await cleanDatabase();
22+
23+
account = await createAccount({ generateKeyPair: true });
24+
client = await createOAuthApplication({
25+
scopes: ["write"],
26+
});
27+
application = await getApplication(client);
28+
accessToken = await getAccessToken(client, account, ["write"]);
29+
});
30+
31+
it("Successfully creates a new status with a valid access token using JSON", async () => {
32+
expect.assertions(7);
33+
34+
const body = JSON.stringify({
35+
status: "Hello world",
36+
media_ids: [],
37+
});
38+
39+
const response = await app.request("/api/v1/statuses", {
40+
method: "POST",
41+
headers: {
42+
authorization: bearerAuthorization(accessToken),
43+
"Content-Type": "application/json",
44+
},
45+
body: body,
46+
});
47+
48+
expect(response.status).toBe(200);
49+
expect(response.headers.get("content-type")).toBe("application/json");
50+
expect(response.headers.get("access-control-allow-origin")).toBe("*");
51+
52+
const json = await response.json();
53+
54+
expect(typeof json).toBe("object");
55+
expect(json.content).toBe("<p>Hello world</p>\n");
56+
expect(json.account.id).toBe(account.id);
57+
expect(json.application.name).toBe(application.name);
58+
});
59+
60+
it("Successfully creates a new status with a valid access token using FormData", async () => {
61+
expect.assertions(7);
62+
63+
const body = new FormData();
64+
body.append("status", "Hello world");
65+
66+
const response = await app.request("/api/v1/statuses", {
67+
method: "POST",
68+
headers: {
69+
authorization: bearerAuthorization(accessToken),
70+
},
71+
body: body,
72+
});
73+
74+
expect(response.status).toBe(200);
75+
expect(response.headers.get("content-type")).toBe("application/json");
76+
expect(response.headers.get("access-control-allow-origin")).toBe("*");
77+
78+
const json = await response.json();
79+
80+
expect(typeof json).toBe("object");
81+
expect(json.content).toBe("<p>Hello world</p>\n");
82+
expect(json.account.id).toBe(account.id);
83+
expect(json.application.name).toBe(application.name);
84+
});
85+
86+
it("Can update a status using JSON", async () => {
87+
const body = JSON.stringify({
88+
status: "Hello world",
89+
});
90+
91+
const createResponse = await app.request("/api/v1/statuses", {
92+
method: "POST",
93+
headers: {
94+
authorization: bearerAuthorization(accessToken),
95+
"Content-Type": "application/json",
96+
},
97+
body: body,
98+
});
99+
100+
expect(createResponse.status).toBe(200);
101+
expect(createResponse.headers.get("content-type")).toBe("application/json");
102+
103+
const createJson = await createResponse.json();
104+
const id = createJson.id;
105+
106+
expect(id).not.toBeNull();
107+
108+
const updateBody = JSON.stringify({
109+
status: "Test Update",
110+
});
111+
const updateResponse = await app.request(`/api/v1/statuses/${id}`, {
112+
method: "PUT",
113+
headers: {
114+
authorization: bearerAuthorization(accessToken),
115+
"Content-Type": "application/json",
116+
},
117+
body: updateBody,
118+
});
119+
120+
expect(updateResponse.status).toBe(200);
121+
expect(updateResponse.headers.get("content-type")).toBe("application/json");
122+
expect(updateResponse.headers.get("access-control-allow-origin")).toBe("*");
123+
124+
const updateJson = await updateResponse.json();
125+
126+
expect(typeof updateJson).toBe("object");
127+
expect(updateJson.content).toBe("<p>Test Update</p>\n");
128+
});
129+
130+
it("Can update a status using FormData", async () => {
131+
const body = JSON.stringify({
132+
status: "Hello world",
133+
media_ids: [],
134+
});
135+
136+
const createResponse = await app.request("/api/v1/statuses", {
137+
method: "POST",
138+
headers: {
139+
authorization: bearerAuthorization(accessToken),
140+
"Content-Type": "application/json",
141+
},
142+
body: body,
143+
});
144+
145+
expect(createResponse.status).toBe(200);
146+
expect(createResponse.headers.get("content-type")).toBe("application/json");
147+
148+
const createJson = await createResponse.json();
149+
const id = createJson.id;
150+
151+
expect(id).not.toBeNull();
152+
153+
const updateBody = new FormData();
154+
updateBody.append("status", "Test Update");
155+
const updateResponse = await app.request(`/api/v1/statuses/${id}`, {
156+
method: "PUT",
157+
headers: {
158+
authorization: bearerAuthorization(accessToken),
159+
},
160+
body: updateBody,
161+
});
162+
163+
expect(updateResponse.status).toBe(200);
164+
expect(updateResponse.headers.get("content-type")).toBe("application/json");
165+
expect(updateResponse.headers.get("access-control-allow-origin")).toBe("*");
166+
167+
const updateJson = await updateResponse.json();
168+
169+
expect(typeof updateJson).toBe("object");
170+
expect(updateJson.content).toBe("<p>Test Update</p>\n");
171+
});
172+
});

0 commit comments

Comments
 (0)