Skip to content

Commit f307eff

Browse files
committed
feat: Add allowedMentions to v1.status.create
1 parent a531d3c commit f307eff

8 files changed

Lines changed: 139 additions & 37 deletions

File tree

src/adapters/action/dispatcher-http.spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ describe("DispatcherHttp", () => {
1414
httpPost.mockResolvedValueOnce({ id: "1" });
1515

1616
httpGet
17-
.mockRejectedValueOnce(new MastoHttpError(404, "Not Found"))
18-
.mockRejectedValueOnce(new MastoHttpError(404, "Not Found"))
17+
.mockRejectedValueOnce(
18+
new MastoHttpError({ statusCode: 404, message: "Not Found" }),
19+
)
20+
.mockRejectedValueOnce(
21+
new MastoHttpError({ statusCode: 404, message: "Not Found" }),
22+
)
1923
.mockResolvedValueOnce({ id: "1", url: "https://example.com" });
2024

2125
const media = await dispatcher.dispatch({
@@ -36,7 +40,9 @@ describe("DispatcherHttp", () => {
3640
});
3741

3842
httpPost.mockResolvedValueOnce({ id: "1" });
39-
httpGet.mockRejectedValue(new MastoHttpError(404, "Not Found"));
43+
httpGet.mockRejectedValue(
44+
new MastoHttpError({ statusCode: 404, message: "Not Found" }),
45+
);
4046

4147
const promise = dispatcher.dispatch({
4248
type: "create",

src/adapters/errors/masto-http-error.spec.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,34 @@ import { MastoHttpError } from "./masto-http-error";
22

33
describe("MastoHttpError", () => {
44
it("creates MastoHttpError", () => {
5-
const error = new MastoHttpError(400, "message");
5+
const error = new MastoHttpError({ statusCode: 400, message: "message" });
66
expect(error.message).toEqual("message");
77
expect(error.statusCode).toEqual(400);
88
});
99

1010
it("creates MastoHttpError with description", () => {
11-
const error = new MastoHttpError(400, "message", "description");
11+
const error = new MastoHttpError({
12+
statusCode: 400,
13+
message: "message",
14+
description: "description",
15+
});
1216
expect(error.message).toEqual("message");
1317
expect(error.statusCode).toEqual(400);
1418
expect(error.description).toEqual("description");
1519
});
1620

1721
it("creates MastoHttpError with details", () => {
18-
const error = new MastoHttpError(400, "message", undefined, {
19-
error: [
20-
{
21-
error: "ERR_INVALID",
22-
description: "Invalid value",
23-
},
24-
],
22+
const error = new MastoHttpError({
23+
statusCode: 400,
24+
message: "message",
25+
details: {
26+
error: [
27+
{
28+
error: "ERR_INVALID",
29+
description: "Invalid value",
30+
},
31+
],
32+
},
2533
});
2634
expect(error.message).toEqual("message");
2735
expect(error.statusCode).toEqual(400);
@@ -34,4 +42,19 @@ describe("MastoHttpError", () => {
3442
],
3543
});
3644
});
45+
46+
it("creates MastoHttpError with additionalProperties", () => {
47+
const error = new MastoHttpError({
48+
statusCode: 400,
49+
message: "message",
50+
additionalProperties: {
51+
foo: "bar",
52+
},
53+
});
54+
expect(error.message).toEqual("message");
55+
expect(error.statusCode).toEqual(400);
56+
expect(error.additionalProperties).toEqual({
57+
foo: "bar",
58+
});
59+
});
3760
});

src/adapters/errors/masto-http-error.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,26 @@ export type MastoHttpErrorDetails = Record<
2323
readonly MastoHttpErrorDetail[]
2424
>;
2525

26+
export interface MastoHttpErrorProps {
27+
readonly statusCode: number;
28+
readonly message: string;
29+
readonly description?: string;
30+
readonly details?: MastoHttpErrorDetails;
31+
readonly additionalProperties?: Record<string, unknown>;
32+
}
33+
2634
export class MastoHttpError extends CustomError {
2735
readonly statusCode: number;
2836
readonly description?: string;
2937
readonly details?: MastoHttpErrorDetails;
38+
readonly additionalProperties?: Record<string, unknown>;
3039

31-
constructor(
32-
statusCode: number,
33-
message: string,
34-
description?: string,
35-
details?: MastoHttpErrorDetails,
36-
options?: ErrorOptions,
37-
) {
38-
super(message, options);
39-
this.statusCode = statusCode;
40-
this.message = message;
41-
this.description = description;
42-
this.details = details;
40+
constructor(props: MastoHttpErrorProps, errorOptions?: ErrorOptions) {
41+
super(props.message, errorOptions);
42+
this.statusCode = props.statusCode;
43+
this.message = props.message;
44+
this.description = props.description;
45+
this.additionalProperties = props.additionalProperties;
46+
this.details = props.details;
4347
}
4448
}

src/adapters/http/http-native-impl.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import node_http from "node:http";
33
import getPort from "get-port";
44

55
import { HttpConfigImpl } from "../config";
6-
import { MastoTimeoutError } from "../errors";
6+
import { type MastoHttpError, MastoTimeoutError } from "../errors";
77
import { SerializerNativeImpl } from "../serializers";
88
import { HttpNativeImpl } from "./http-native-impl";
99

@@ -71,4 +71,32 @@ describe("HttpNativeImpl", () => {
7171

7272
server.close();
7373
});
74+
75+
it("collects any additional properties if provided", async () => {
76+
const server = node_http.createServer((_, res) => {
77+
res.writeHead(400, { "Content-Type": "application/json" });
78+
res.end(JSON.stringify({ error: "unknown error", foo: "bar" }));
79+
});
80+
81+
const port = await getPort();
82+
server.listen(port);
83+
84+
const serializer = new SerializerNativeImpl();
85+
const http = new HttpNativeImpl(
86+
serializer,
87+
new HttpConfigImpl({ url: `http://localhost:${port}` }, serializer),
88+
);
89+
90+
const error = await http.get("/").then(
91+
() => {
92+
throw new Error("should not happen");
93+
},
94+
(error) => error as MastoHttpError,
95+
);
96+
97+
expect(error.message).toEqual("unknown error");
98+
expect((error.additionalProperties as any).foo).toEqual("bar");
99+
100+
server.close();
101+
});
74102
});

src/adapters/http/http-native-impl.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,21 @@ export class HttpNativeImpl extends BaseHttp implements Http {
9797
}
9898

9999
const data = this.serializer.deserialize(encoding, await error.text());
100+
const {
101+
error: message,
102+
errorDescription,
103+
details,
104+
...additionalProperties
105+
} = data;
100106

101107
return new MastoHttpError(
102-
error.status,
103-
data.error as string,
104-
data.errorDescription as string,
105-
data.details as MastoHttpErrorDetails,
108+
{
109+
statusCode: error.status,
110+
message: message as string,
111+
description: errorDescription as string,
112+
details: details as MastoHttpErrorDetails,
113+
additionalProperties,
114+
},
106115
{ cause: error },
107116
);
108117
}

src/mastodon/rest/v1/status-repository.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export interface CreateStatusParamsBase {
2323
readonly visibility?: StatusVisibility | null;
2424
/** ISO 639 language code for this status. */
2525
readonly language?: string | null;
26+
/** https://github.com/mastodon/mastodon/pull/18350 */
27+
readonly allowedMentions?: readonly string[] | null;
2628
}
2729

2830
export interface CreateStatusPollParam {

tests/rest/v1/accounts.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@ describe("account", () => {
2323

2424
it("throws an error if registration is malformed", () => {
2525
return sessions.use(async (session) => {
26-
let error: unknown;
27-
28-
try {
29-
await session.rest.v1.accounts.create({
26+
const error = await session.rest.v1.accounts
27+
.create({
3028
username: "",
3129
email: "",
3230
password: "",
3331
agreement: false,
3432
locale: "hello",
35-
});
36-
} catch (error_) {
37-
error = error_ as Error;
38-
}
33+
})
34+
.then(
35+
() => {
36+
throw new Error("should not be called");
37+
},
38+
(error) => error,
39+
);
3940

4041
assert(error instanceof MastoHttpError);
4142
expect(error.statusCode).toBe(422);

tests/rest/v1/statuses.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import crypto from "node:crypto";
22

3+
import { MastoHttpError } from "../../../src";
4+
35
describe("status", () => {
46
it("creates, updates, and removes a status", () => {
57
return sessions.use(async (client) => {
@@ -210,4 +212,31 @@ describe("status", () => {
210212
await client.rest.v1.statuses.$select(status.id).remove();
211213
});
212214
});
215+
216+
it("only mentions listed users when allowedMentions is specified", () => {
217+
return sessions.use(2, async ([alice, bob]) => {
218+
const error = await alice.rest.v1.statuses
219+
.create({
220+
status: `@${bob.acct} hello`,
221+
allowedMentions: [],
222+
})
223+
.then(
224+
() => {
225+
throw new Error("Unexpected success");
226+
},
227+
(_error) => {
228+
if (_error instanceof MastoHttpError) return _error;
229+
throw _error;
230+
},
231+
);
232+
233+
expect(error.additionalProperties).toEqual({
234+
unexpectedAccounts: [
235+
expect.objectContaining({
236+
id: bob.id,
237+
}),
238+
],
239+
});
240+
});
241+
});
213242
});

0 commit comments

Comments
 (0)