@codybrom/denim 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mod_test.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  // mod_test.ts
2
+ import type { ThreadsPostRequest } from "./types.ts";
2
3
  import {
3
4
  assertEquals,
4
5
  assertRejects,
@@ -8,494 +9,427 @@ import {
8
9
  publishThreadsContainer,
9
10
  createCarouselItem,
10
11
  getPublishingLimit,
11
- type ThreadsPostRequest,
12
+ getThreadsList,
13
+ getSingleThread,
12
14
  } from "./mod.ts";
15
+ import { MockThreadsAPI } from "./mock_threads_api.ts";
13
16
 
14
- // Mock fetch response
15
- globalThis.fetch = (
16
- input: string | URL | Request,
17
- init?: RequestInit
18
- ): Promise<Response> => {
19
- const url =
20
- typeof input === "string"
21
- ? input
22
- : input instanceof URL
23
- ? input.toString()
24
- : input.url;
25
-
26
- const body =
27
- init?.body instanceof URLSearchParams ? init.body : new URLSearchParams();
28
-
29
- if (url.includes("threads")) {
30
- if (url.includes("threads_publish")) {
31
- return Promise.resolve({
32
- ok: true,
33
- status: 200,
34
- statusText: "OK",
35
- text: () => Promise.resolve(JSON.stringify({ id: "published123" })),
36
- } as Response);
37
- }
38
-
39
- if (body.get("is_carousel_item") === "true") {
40
- if (body.get("access_token") === "invalid_token") {
41
- return Promise.resolve({
42
- ok: false,
43
- status: 400,
44
- statusText: "Bad Request",
45
- text: () =>
46
- Promise.resolve(JSON.stringify({ error: "Invalid access token" })),
47
- } as Response);
48
- }
49
- return Promise.resolve({
50
- ok: true,
51
- status: 200,
52
- statusText: "OK",
53
- text: () => Promise.resolve(JSON.stringify({ id: "item123" })),
54
- } as Response);
55
- }
17
+ Deno.test("Threads API", async (t) => {
18
+ let mockAPI: MockThreadsAPI;
56
19
 
57
- return Promise.resolve({
58
- ok: true,
59
- status: 200,
60
- statusText: "OK",
61
- text: () => Promise.resolve(JSON.stringify({ id: "container123" })),
62
- } as Response);
20
+ function setupMockAPI() {
21
+ mockAPI = new MockThreadsAPI();
22
+ (globalThis as { threadsAPI?: MockThreadsAPI }).threadsAPI = mockAPI;
63
23
  }
64
24
 
65
- return Promise.resolve({
66
- ok: false,
67
- status: 500,
68
- statusText: "Internal Server Error",
69
- text: () => Promise.resolve("Error"),
70
- } as Response);
71
- };
72
-
73
- Deno.test(
74
- "createThreadsContainer should return container ID for basic text post",
75
- async () => {
76
- const requestData: ThreadsPostRequest = {
77
- userId: "12345",
78
- accessToken: "token",
79
- mediaType: "TEXT",
80
- text: "Hello, Threads!",
81
- };
82
-
83
- const containerId = await createThreadsContainer(requestData);
84
- assertEquals(containerId, "container123");
85
- }
86
- );
87
-
88
- Deno.test(
89
- "createThreadsContainer should return container ID with text post with link attachment, reply control, and allowlisted countries",
90
- async () => {
91
- const requestData: ThreadsPostRequest = {
92
- userId: "12345",
93
- accessToken: "token",
94
- mediaType: "TEXT",
95
- text: "Hello, Threads!",
96
- linkAttachment: "https://example.com",
97
- replyControl: "everyone",
98
- allowlistedCountryCodes: ["US", "CA"],
99
- };
100
-
101
- const containerId = await createThreadsContainer(requestData);
102
- assertEquals(containerId, "container123");
25
+ function teardownMockAPI() {
26
+ delete (globalThis as { threadsAPI?: MockThreadsAPI }).threadsAPI;
103
27
  }
104
- );
105
-
106
- Deno.test(
107
- "createThreadsContainer should handle image post with alt text",
108
- async () => {
109
- const requestData: ThreadsPostRequest = {
110
- userId: "12345",
111
- accessToken: "token",
112
- mediaType: "IMAGE",
113
- text: "Check out this image!",
114
- imageUrl: "https://example.com/image.jpg",
115
- altText: "A beautiful sunset",
116
- };
117
-
118
- const containerId = await createThreadsContainer(requestData);
119
- assertEquals(containerId, "container123");
120
- }
121
- );
122
-
123
- Deno.test(
124
- "createThreadsContainer should handle video post with all features",
125
- async () => {
126
- const requestData: ThreadsPostRequest = {
127
- userId: "12345",
128
- accessToken: "token",
129
- mediaType: "VIDEO",
130
- text: "Watch this video!",
131
- videoUrl: "https://example.com/video.mp4",
132
- altText: "A tutorial video",
133
- replyControl: "mentioned_only",
134
- allowlistedCountryCodes: ["US", "GB"],
135
- };
136
-
137
- const containerId = await createThreadsContainer(requestData);
138
- assertEquals(containerId, "container123");
139
- }
140
- );
141
-
142
- Deno.test("createThreadsContainer should throw error on failure", async () => {
143
- const requestData: ThreadsPostRequest = {
144
- userId: "12345",
145
- accessToken: "token",
146
- mediaType: "TEXT",
147
- text: "Hello, Threads!",
148
- linkAttachment: "https://example.com",
149
- };
150
-
151
- globalThis.fetch = (): Promise<Response> =>
152
- Promise.resolve({
153
- ok: false,
154
- status: 500,
155
- statusText: "Internal Server Error",
156
- text: () => Promise.resolve("Error"),
157
- } as Response);
158
-
159
- await assertRejects(
160
- async () => {
161
- await createThreadsContainer(requestData);
162
- },
163
- Error,
164
- "Failed to create Threads container"
165
- );
166
- });
167
- Deno.test("createCarouselItem should return item ID", async () => {
168
- const requestData = {
169
- userId: "12345",
170
- accessToken: "token",
171
- mediaType: "IMAGE" as const,
172
- imageUrl: "https://example.com/image.jpg",
173
- altText: "Test image",
174
- };
175
-
176
- globalThis.fetch = (
177
- _input: string | URL | Request,
178
- init?: RequestInit
179
- ): Promise<Response> => {
180
- const body =
181
- init?.body instanceof URLSearchParams ? init.body : new URLSearchParams();
182
- if (body.get("is_carousel_item") === "true") {
183
- return Promise.resolve({
184
- ok: true,
185
- status: 200,
186
- statusText: "OK",
187
- text: () => Promise.resolve(JSON.stringify({ id: "item123" })),
188
- } as Response);
189
- }
190
- return Promise.resolve({
191
- ok: false,
192
- status: 500,
193
- statusText: "Internal Server Error",
194
- text: () => Promise.resolve("Error"),
195
- } as Response);
196
- };
197
-
198
- const itemId = await createCarouselItem(requestData);
199
- assertEquals(itemId, "item123");
200
- });
201
-
202
- Deno.test("createCarouselItem should handle video items", async () => {
203
- const requestData = {
204
- userId: "12345",
205
- accessToken: "token",
206
- mediaType: "VIDEO" as const,
207
- videoUrl: "https://example.com/video.mp4",
208
- altText: "Test video",
209
- };
210
-
211
- globalThis.fetch = (
212
- _input: string | URL | Request,
213
- init?: RequestInit
214
- ): Promise<Response> => {
215
- const body =
216
- init?.body instanceof URLSearchParams ? init.body : new URLSearchParams();
217
- if (body.get("is_carousel_item") === "true") {
218
- return Promise.resolve({
219
- ok: true,
220
- status: 200,
221
- statusText: "OK",
222
- text: () => Promise.resolve(JSON.stringify({ id: "item123" })),
223
- } as Response);
224
- }
225
- return Promise.resolve({
226
- ok: false,
227
- status: 500,
228
- statusText: "Internal Server Error",
229
- text: () => Promise.resolve("Error"),
230
- } as Response);
231
- };
232
-
233
- const itemId = await createCarouselItem(requestData);
234
- assertEquals(itemId, "item123");
235
- });
236
28
 
237
- Deno.test("createThreadsContainer should handle carousel post", async () => {
238
- const requestData: ThreadsPostRequest = {
239
- userId: "12345",
240
- accessToken: "token",
241
- mediaType: "CAROUSEL",
242
- text: "Check out this carousel!",
243
- children: ["item123", "item456"],
244
- replyControl: "everyone",
245
- allowlistedCountryCodes: ["US", "CA"],
246
- };
247
-
248
- const containerId = await createThreadsContainer(requestData);
249
- assertEquals(containerId, "container123");
250
- });
251
-
252
- Deno.test("createCarouselItem should throw error on failure", async () => {
253
- const requestData = {
254
- userId: "12345",
255
- accessToken: "invalid_token",
256
- mediaType: "IMAGE" as const,
257
- imageUrl: "https://example.com/image.jpg",
258
- };
259
-
260
- await assertRejects(
261
- () => createCarouselItem(requestData),
262
- Error,
263
- "Failed to create carousel item"
264
- );
265
- });
266
-
267
- Deno.test("publishThreadsContainer should return published ID", async () => {
268
- const userId = "12345";
269
- const accessToken = "token";
270
- const containerId = "container123";
271
-
272
- const publishedId = await publishThreadsContainer(
273
- userId,
274
- accessToken,
275
- containerId
276
- );
277
- assertEquals(publishedId, "published123");
278
- });
279
-
280
- Deno.test("publishThreadsContainer should throw error on failure", async () => {
281
- const userId = "12345";
282
- const accessToken = "token";
283
- const containerId = "container123";
284
-
285
- globalThis.fetch = (
286
- _input: string | URL | Request,
287
- _init?: RequestInit
288
- ): Promise<Response> =>
289
- Promise.resolve({
290
- ok: false,
291
- status: 500,
292
- statusText: "Internal Server Error",
293
- text: () => Promise.resolve("Error"),
294
- } as Response);
295
-
296
- await assertRejects(
297
- async () => {
298
- await publishThreadsContainer(userId, accessToken, containerId);
299
- },
300
- Error,
301
- "Failed to publish Threads container"
302
- );
303
- });
304
-
305
- Deno.test(
306
- "createThreadsContainer should throw error when imageUrl is provided for non-IMAGE type",
307
- async () => {
308
- const requestData: ThreadsPostRequest = {
309
- userId: "12345",
310
- accessToken: "token",
311
- mediaType: "TEXT",
312
- text: "This shouldn't work",
313
- imageUrl: "https://example.com/image.jpg",
314
- };
315
-
316
- await assertRejects(
317
- () => createThreadsContainer(requestData),
318
- Error,
319
- "imageUrl can only be used with IMAGE media type"
29
+ await t.step("createThreadsContainer", async (t) => {
30
+ await t.step("should return container ID for basic text post", async () => {
31
+ setupMockAPI();
32
+ const requestData: ThreadsPostRequest = {
33
+ userId: "12345",
34
+ accessToken: "token",
35
+ mediaType: "TEXT",
36
+ text: "Hello, Threads!",
37
+ };
38
+
39
+ const result = await createThreadsContainer(requestData);
40
+ if (typeof result === "string") {
41
+ assertEquals(result.length > 0, true);
42
+ } else {
43
+ assertEquals(typeof result.id, "string");
44
+ assertEquals(result.id.length > 0, true);
45
+ }
46
+ teardownMockAPI();
47
+ });
48
+
49
+ await t.step("should handle image post with alt text", async () => {
50
+ setupMockAPI();
51
+ const requestData: ThreadsPostRequest = {
52
+ userId: "12345",
53
+ accessToken: "token",
54
+ mediaType: "IMAGE",
55
+ text: "Check out this image!",
56
+ imageUrl: "https://example.com/image.jpg",
57
+ altText: "A beautiful sunset",
58
+ };
59
+
60
+ const containerId = await createThreadsContainer(requestData);
61
+ if (typeof containerId === "string") {
62
+ assertEquals(containerId.length > 0, true);
63
+ } else {
64
+ assertEquals(typeof containerId.id, "string");
65
+ assertEquals(containerId.id.length > 0, true);
66
+ }
67
+ teardownMockAPI();
68
+ });
69
+
70
+ await t.step("should handle video post with all features", async () => {
71
+ setupMockAPI();
72
+ const requestData: ThreadsPostRequest = {
73
+ userId: "12345",
74
+ accessToken: "token",
75
+ mediaType: "VIDEO",
76
+ text: "Watch this video!",
77
+ videoUrl: "https://example.com/video.mp4",
78
+ altText: "A tutorial video",
79
+ replyControl: "mentioned_only",
80
+ allowlistedCountryCodes: ["US", "GB"],
81
+ };
82
+
83
+ const containerId = await createThreadsContainer(requestData);
84
+ if (typeof containerId === "string") {
85
+ assertEquals(containerId.length > 0, true);
86
+ } else {
87
+ assertEquals(typeof containerId.id, "string");
88
+ assertEquals(containerId.id.length > 0, true);
89
+ }
90
+ teardownMockAPI();
91
+ });
92
+
93
+ await t.step("should throw error on failure", async () => {
94
+ setupMockAPI();
95
+ const requestData: ThreadsPostRequest = {
96
+ userId: "12345",
97
+ accessToken: "invalid_token",
98
+ mediaType: "TEXT",
99
+ text: "Hello, Threads!",
100
+ linkAttachment: "https://example.com",
101
+ };
102
+
103
+ // Mock the error in the MockThreadsAPI
104
+ mockAPI.setErrorMode(true);
105
+
106
+ await assertRejects(
107
+ () => createThreadsContainer(requestData),
108
+ Error,
109
+ "Failed to create Threads container"
110
+ );
111
+ teardownMockAPI();
112
+ });
113
+
114
+ await t.step(
115
+ "should throw error when CAROUSEL type is used without children",
116
+ async () => {
117
+ const requestData: ThreadsPostRequest = {
118
+ userId: "12345",
119
+ accessToken: "token",
120
+ mediaType: "CAROUSEL",
121
+ text: "This carousel has no items",
122
+ };
123
+
124
+ await assertRejects(
125
+ async () => await createThreadsContainer(requestData),
126
+ Error,
127
+ "CAROUSEL media type requires at least 2 children"
128
+ );
129
+ }
320
130
  );
321
- }
322
- );
323
-
324
- Deno.test(
325
- "createThreadsContainer should throw error when videoUrl is provided for non-VIDEO type",
326
- async () => {
327
- const requestData: ThreadsPostRequest = {
328
- userId: "12345",
329
- accessToken: "token",
330
- mediaType: "IMAGE",
331
- imageUrl: "https://example.com/image.jpg",
332
- videoUrl: "https://example.com/video.mp4",
333
- };
334
-
335
- await assertRejects(
336
- () => createThreadsContainer(requestData),
337
- Error,
338
- "videoUrl can only be used with VIDEO media type"
131
+
132
+ await t.step(
133
+ "should throw error when imageUrl is provided for non-IMAGE type",
134
+ async () => {
135
+ const requestData: ThreadsPostRequest = {
136
+ userId: "12345",
137
+ accessToken: "token",
138
+ mediaType: "TEXT",
139
+ text: "This shouldn't work",
140
+ imageUrl: "https://example.com/image.jpg",
141
+ };
142
+
143
+ await assertRejects(
144
+ () => createThreadsContainer(requestData),
145
+ Error,
146
+ "imageUrl can only be used with IMAGE media type"
147
+ );
148
+ }
339
149
  );
340
- }
341
- );
342
-
343
- Deno.test(
344
- "createThreadsContainer should throw error when linkAttachment is provided for non-TEXT type",
345
- async () => {
346
- const requestData: ThreadsPostRequest = {
347
- userId: "12345",
348
- accessToken: "token",
349
- mediaType: "IMAGE",
350
- imageUrl: "https://example.com/image.jpg",
351
- linkAttachment: "https://example.com",
352
- };
353
-
354
- await assertRejects(
355
- () => createThreadsContainer(requestData),
356
- Error,
357
- "linkAttachment can only be used with TEXT media type"
150
+
151
+ await t.step(
152
+ "should throw error when videoUrl is provided for non-VIDEO type",
153
+ async () => {
154
+ const requestData: ThreadsPostRequest = {
155
+ userId: "12345",
156
+ accessToken: "token",
157
+ mediaType: "IMAGE",
158
+ imageUrl: "https://example.com/image.jpg",
159
+ videoUrl: "https://example.com/video.mp4",
160
+ };
161
+
162
+ await assertRejects(
163
+ () => createThreadsContainer(requestData),
164
+ Error,
165
+ "videoUrl can only be used with VIDEO media type"
166
+ );
167
+ }
358
168
  );
359
- }
360
- );
361
-
362
- Deno.test(
363
- "createThreadsContainer should throw error when children is provided for non-CAROUSEL type",
364
- async () => {
365
- const requestData: ThreadsPostRequest = {
366
- userId: "12345",
367
- accessToken: "token",
368
- mediaType: "IMAGE",
369
- imageUrl: "https://example.com/image.jpg",
370
- children: ["item1", "item2"],
371
- };
372
-
373
- await assertRejects(
169
+
170
+ await t.step(
171
+ "should throw error when linkAttachment is provided for non-TEXT type",
374
172
  async () => {
375
- await createThreadsContainer(requestData);
376
- },
377
- Error,
378
- "Failed to create Threads container"
173
+ const requestData: ThreadsPostRequest = {
174
+ userId: "12345",
175
+ accessToken: "token",
176
+ mediaType: "IMAGE",
177
+ imageUrl: "https://example.com/image.jpg",
178
+ linkAttachment: "https://example.com",
179
+ };
180
+
181
+ await assertRejects(
182
+ () => createThreadsContainer(requestData),
183
+ Error,
184
+ "linkAttachment can only be used with TEXT media type"
185
+ );
186
+ }
379
187
  );
380
- }
381
- );
382
-
383
- Deno.test(
384
- "createThreadsContainer should throw error when CAROUSEL type is used without children",
385
- async () => {
386
- const requestData: ThreadsPostRequest = {
387
- userId: "12345",
388
- accessToken: "token",
389
- mediaType: "CAROUSEL",
390
- text: "This carousel has no items",
391
- };
392
-
393
- await assertRejects(
188
+
189
+ await t.step(
190
+ "should throw error when children is provided for non-CAROUSEL type",
394
191
  async () => {
395
- await createThreadsContainer(requestData);
396
- },
397
- Error,
398
- "Failed to create Threads container"
192
+ const requestData: ThreadsPostRequest = {
193
+ userId: "12345",
194
+ accessToken: "token",
195
+ mediaType: "IMAGE",
196
+ imageUrl: "https://example.com/image.jpg",
197
+ children: ["item1", "item2"],
198
+ };
199
+
200
+ await assertRejects(
201
+ async () => {
202
+ await createThreadsContainer(requestData);
203
+ },
204
+ Error,
205
+ "Failed to create Threads container"
206
+ );
207
+ }
399
208
  );
400
- }
401
- );
402
-
403
- Deno.test(
404
- "createThreadsContainer should not throw error when attributes are used correctly",
405
- async () => {
406
- const textRequest: ThreadsPostRequest = {
407
- userId: "12345",
408
- accessToken: "token",
209
+ });
210
+
211
+ await t.step("publishThreadsContainer", async (t) => {
212
+ await t.step("should publish container successfully", async () => {
213
+ setupMockAPI();
214
+ const userId = "12345";
215
+ const accessToken = "token";
216
+ const containerId = await createThreadsContainer({
217
+ userId,
218
+ accessToken,
219
+ mediaType: "TEXT",
220
+ text: "Test post",
221
+ });
222
+
223
+ const result = await publishThreadsContainer(
224
+ userId,
225
+ accessToken,
226
+ typeof containerId === "string" ? containerId : containerId.id
227
+ );
228
+ if (typeof result === "string") {
229
+ assertEquals(result.length > 0, true);
230
+ } else {
231
+ assertEquals(typeof result.id, "string");
232
+ assertEquals(result.id.length > 0, true);
233
+ }
234
+ teardownMockAPI();
235
+ });
236
+
237
+ await t.step("should throw error on failure", async () => {
238
+ setupMockAPI();
239
+ const userId = "12345";
240
+ const accessToken = "invalid_token";
241
+ const containerId = "invalid_container";
242
+
243
+ // Mock the error in the MockThreadsAPI
244
+ mockAPI.setErrorMode(true);
245
+
246
+ await assertRejects(
247
+ () => publishThreadsContainer(userId, accessToken, containerId),
248
+ Error,
249
+ "Failed to publish Threads container"
250
+ );
251
+ teardownMockAPI();
252
+ });
253
+
254
+ await t.step("should return permalink when requested", async () => {
255
+ setupMockAPI();
256
+ const userId = "12345";
257
+ const accessToken = "token";
258
+ const containerId = await createThreadsContainer({
259
+ userId,
260
+ accessToken,
261
+ mediaType: "TEXT",
262
+ text: "Test post with permalink",
263
+ });
264
+
265
+ const result = await publishThreadsContainer(
266
+ userId,
267
+ accessToken,
268
+ typeof containerId === "string" ? containerId : containerId.id,
269
+ true // Request permalink
270
+ );
271
+
272
+ if (typeof result === "string") {
273
+ throw new Error("Expected an object with permalink, but got a string");
274
+ } else {
275
+ assertEquals(typeof result, "object");
276
+ assertEquals(typeof result.id, "string");
277
+ assertEquals(typeof result.permalink, "string");
278
+ assertEquals(result.permalink.startsWith("https://"), true);
279
+ }
280
+ teardownMockAPI();
281
+ });
282
+
283
+ await t.step("should not return permalink when not requested", async () => {
284
+ setupMockAPI();
285
+ const userId = "12345";
286
+ const accessToken = "token";
287
+ const containerId = await createThreadsContainer({
288
+ userId,
289
+ accessToken,
290
+ mediaType: "TEXT",
291
+ text: "Test post without permalink",
292
+ });
293
+
294
+ const result = await publishThreadsContainer(
295
+ userId,
296
+ accessToken,
297
+ typeof containerId === "string" ? containerId : containerId.id,
298
+ false // Don't request permalink
299
+ );
300
+
301
+ assertEquals(typeof result, "string");
302
+ teardownMockAPI();
303
+ });
304
+ });
305
+
306
+ await t.step("createCarouselItem", async (t) => {
307
+ await t.step("should return item ID", async () => {
308
+ setupMockAPI();
309
+ const requestData = {
310
+ userId: "12345",
311
+ accessToken: "token",
312
+ mediaType: "IMAGE" as const,
313
+ imageUrl: "https://example.com/image.jpg",
314
+ altText: "Test image",
315
+ };
316
+
317
+ const itemId = await createCarouselItem(requestData);
318
+ if (typeof itemId === "string") {
319
+ assertEquals(itemId.length > 0, true);
320
+ } else {
321
+ assertEquals(typeof itemId.id, "string");
322
+ assertEquals(itemId.id.length > 0, true);
323
+ }
324
+ teardownMockAPI();
325
+ });
326
+
327
+ await t.step("should handle video items", async () => {
328
+ setupMockAPI();
329
+ const requestData = {
330
+ userId: "12345",
331
+ accessToken: "token",
332
+ mediaType: "VIDEO" as const,
333
+ videoUrl: "https://example.com/video.mp4",
334
+ altText: "Test video",
335
+ };
336
+
337
+ const itemId = await createCarouselItem(requestData);
338
+ if (typeof itemId === "string") {
339
+ assertEquals(itemId.length > 0, true);
340
+ } else {
341
+ assertEquals(typeof itemId.id, "string");
342
+ assertEquals(itemId.id.length > 0, true);
343
+ }
344
+ teardownMockAPI();
345
+ });
346
+ });
347
+
348
+ await t.step("getThreadsList", async () => {
349
+ setupMockAPI();
350
+ const userId = "12345";
351
+ const accessToken = "valid_token";
352
+
353
+ // Create some test posts
354
+ await createThreadsContainer({
355
+ userId,
356
+ accessToken,
409
357
  mediaType: "TEXT",
410
- text: "This is a text post",
411
- linkAttachment: "https://example.com",
412
- };
413
-
414
- const imageRequest: ThreadsPostRequest = {
415
- userId: "12345",
416
- accessToken: "token",
417
- mediaType: "IMAGE",
418
- imageUrl: "https://example.com/image.jpg",
419
- altText: "An example image",
420
- };
421
-
422
- const videoRequest: ThreadsPostRequest = {
423
- userId: "12345",
424
- accessToken: "token",
425
- mediaType: "VIDEO",
426
- videoUrl: "https://example.com/video.mp4",
427
- altText: "An example video",
428
- };
429
-
430
- const carouselRequest: ThreadsPostRequest = {
431
- userId: "12345",
432
- accessToken: "token",
433
- mediaType: "CAROUSEL",
434
- text: "A carousel post",
435
- children: ["item1", "item2"],
436
- };
437
-
438
- const textContainerId = await createThreadsContainer(textRequest);
439
- const imageContainerId = await createThreadsContainer(imageRequest);
440
- const videoContainerId = await createThreadsContainer(videoRequest);
441
- const carouselContainerId = await createThreadsContainer(carouselRequest);
442
-
443
- assertEquals(textContainerId, "container123");
444
- assertEquals(imageContainerId, "container123");
445
- assertEquals(videoContainerId, "container123");
446
- assertEquals(carouselContainerId, "container123");
447
- }
448
- );
358
+ text: "Test post 1",
359
+ });
360
+ await createThreadsContainer({
361
+ userId,
362
+ accessToken,
363
+ mediaType: "TEXT",
364
+ text: "Test post 2",
365
+ });
366
+
367
+ const result = await getThreadsList(userId, accessToken);
368
+ assertEquals(Array.isArray(result.data), true);
369
+ assertEquals(result.data.length > 0, true);
370
+ assertEquals(result.data[0].text, "Test post 1");
371
+ assertEquals(result.data[1].text, "Test post 2");
372
+ if (result.paging) {
373
+ assertEquals(typeof result.paging.cursors.before, "string");
374
+ assertEquals(typeof result.paging.cursors.after, "string");
375
+ }
376
+ teardownMockAPI();
377
+ });
449
378
 
450
- Deno.test(
451
- "getPublishingLimit should return rate limit information",
452
- async () => {
379
+ await t.step("getSingleThread", async () => {
380
+ setupMockAPI();
453
381
  const userId = "12345";
454
382
  const accessToken = "valid_token";
383
+ const testText = "Test post for getSingleThread";
455
384
 
456
- globalThis.fetch = (_input: string | URL | Request): Promise<Response> => {
457
- return Promise.resolve({
458
- ok: true,
459
- status: 200,
460
- json: () =>
461
- Promise.resolve({
462
- data: [
463
- {
464
- quota_usage: 10,
465
- config: {
466
- quota_total: 250,
467
- quota_duration: 86400,
468
- },
469
- },
470
- ],
471
- }),
472
- } as Response);
473
- };
474
-
475
- const result = await getPublishingLimit(userId, accessToken);
476
- assertEquals(result.quota_usage, 10);
477
- assertEquals(result.config.quota_total, 250);
478
- assertEquals(result.config.quota_duration, 86400);
479
- }
480
- );
481
-
482
- Deno.test("getPublishingLimit should throw error on failure", async () => {
483
- const userId = "12345";
484
- const accessToken = "invalid_token";
485
-
486
- globalThis.fetch = (_input: string | URL | Request): Promise<Response> => {
487
- return Promise.resolve({
488
- ok: false,
489
- status: 400,
490
- statusText: "Bad Request",
491
- json: () =>
492
- Promise.resolve({ error: { message: "Invalid access token" } }),
493
- } as Response);
494
- };
495
-
496
- await assertRejects(
497
- () => getPublishingLimit(userId, accessToken),
498
- Error,
499
- "Failed to get publishing limit"
500
- );
385
+ const containerId = await createThreadsContainer({
386
+ userId,
387
+ accessToken,
388
+ mediaType: "TEXT",
389
+ text: testText,
390
+ });
391
+ const mediaId = await publishThreadsContainer(
392
+ userId,
393
+ accessToken,
394
+ typeof containerId === "string" ? containerId : containerId.id
395
+ );
396
+
397
+ const result = await getSingleThread(
398
+ typeof mediaId === "string" ? mediaId : mediaId.id,
399
+ accessToken
400
+ );
401
+ assertEquals(typeof result.id, "string");
402
+ assertEquals(result.text, testText);
403
+ teardownMockAPI();
404
+ });
405
+
406
+ await t.step("getPublishingLimit", async (t) => {
407
+ await t.step("should return rate limit information", async () => {
408
+ setupMockAPI();
409
+ const userId = "12345";
410
+ const accessToken = "valid_token";
411
+
412
+ const result = await getPublishingLimit(userId, accessToken);
413
+ assertEquals(typeof result.quota_usage, "number");
414
+ assertEquals(typeof result.config.quota_total, "number");
415
+ assertEquals(typeof result.config.quota_duration, "number");
416
+ teardownMockAPI();
417
+ });
418
+
419
+ await t.step("should throw error on failure", async () => {
420
+ setupMockAPI();
421
+ const userId = "12345";
422
+ const accessToken = "invalid_token";
423
+
424
+ // Mock the error in the MockThreadsAPI
425
+ mockAPI.setErrorMode(true);
426
+
427
+ await assertRejects(
428
+ () => getPublishingLimit(userId, accessToken),
429
+ Error,
430
+ "Failed to get publishing limit"
431
+ );
432
+ teardownMockAPI();
433
+ });
434
+ });
501
435
  });