@yaebal/again 0.0.3 → 0.0.4

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/lib/index.test.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { TelegramError } from "@yaebal/core";
3
+ import { Composer, TelegramError } from "@yaebal/core";
4
+ import { apiError, createTestEnv } from "@yaebal/test";
4
5
  import { autoRetry, decideRetry } from "./index.js";
6
+ import { againTestPack } from "./test-pack.js";
5
7
  test("429 with retry_after waits exactly that long", () => {
6
8
  assert.deepEqual(decideRetry(new TelegramError("sendMessage", 429, "Too Many Requests: retry after 7"), 1), { retry: true, delayMs: 7000 });
7
9
  });
@@ -37,4 +39,21 @@ test("autoRetry can be installed as a bot plugin", () => {
37
39
  autoRetry({ maxRetries: 1 })({ api });
38
40
  assert.equal(typeof hook, "function");
39
41
  });
42
+ test("againTestPack: a bot under test transparently retries a 429 and succeeds", async () => {
43
+ const bot = new Composer().on("message:text", (ctx) => ctx.reply("pong"));
44
+ const env = createTestEnv(bot, { packs: [againTestPack({ maxRetries: 2 })] });
45
+ env.onApi("sendMessage", apiError(429, "Too Many Requests: retry after 0"), { times: 1 });
46
+ await env.createUser().sendMessage("ping");
47
+ assert.equal(env.callsTo("sendMessage").length, 2); // first attempt failed, second succeeded
48
+ assert.equal(env.lastApiCall("sendMessage")?.params?.text, "pong");
49
+ });
50
+ test("againTestPack: exhausting maxRetries still throws", async () => {
51
+ const bot = new Composer().on("message:text", async (ctx) => {
52
+ await assert.rejects(ctx.reply("pong"), TelegramError);
53
+ });
54
+ const env = createTestEnv(bot, { packs: [againTestPack({ maxRetries: 1 })] });
55
+ env.onApi("sendMessage", apiError(429, "Too Many Requests: retry after 0"));
56
+ await env.createUser().sendMessage("ping");
57
+ assert.equal(env.callsTo("sendMessage").length, 2); // 1 initial attempt + 1 retry, then gives up
58
+ });
40
59
  //# sourceMappingURL=index.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEpD,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;IACzD,MAAM,CAAC,SAAS,CACf,WAAW,CAAC,IAAI,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE,kCAAkC,CAAC,EAAE,CAAC,CAAC,EACzF,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAC9B,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACjD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE;QACtF,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,IAAI;KACb,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;IAClD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACjG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mCAAmC,EAAE,GAAG,EAAE;IAC9C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAC/F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qCAAqC,EAAE,GAAG,EAAE;IAChD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACrC,MAAM,CAAC,SAAS,CACf,WAAW,CAAC,IAAI,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EACpF,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAC9B,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC5C,MAAM,CAAC,KAAK,CACX,WAAW,CAAC,IAAI,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,qBAAqB,CAAC,EAAE,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,EAC9F,SAAS,CACT,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACvD,IAAI,IAA2B,CAAC;IAChC,MAAM,GAAG,GAAG;QACX,OAAO,EAAE,CAAC,CAAY,EAAE,EAAE;YACzB,IAAI,GAAG,CAAC,CAAC;YACT,OAAO,GAAU,CAAC;QACnB,CAAC;KACM,CAAC;IAET,SAAS,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAW,CAAC,CAAC;IAE/C,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,UAAU,CAAC,CAAC;AACvC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { TestPack } from "@yaebal/test";
2
+ import { type AutoRetryOptions } from "./index.js";
3
+ /**
4
+ * wires {@link autoRetry} onto a `TestEnv`'s mock api — pass to `createTestEnv(bot, { packs: [againTestPack()] })`
5
+ * so retry tests don't need to call `autoRetry(env.api)` by hand.
6
+ *
7
+ * @example
8
+ * const env = createTestEnv(bot, { packs: [againTestPack({ maxRetries: 2 })] });
9
+ * env.onApi("sendMessage", apiError(429, "retry after 0"), { times: 1 });
10
+ * await env.createUser().sendCommand("start"); // retries once, then succeeds
11
+ */
12
+ export declare function againTestPack(options?: AutoRetryOptions): TestPack;
13
+ //# sourceMappingURL=test-pack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-pack.d.ts","sourceRoot":"","sources":["../src/test-pack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,YAAY,CAAC;AAE9D;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,QAAQ,CAOlE"}
@@ -0,0 +1,19 @@
1
+ import { autoRetry } from "./index.js";
2
+ /**
3
+ * wires {@link autoRetry} onto a `TestEnv`'s mock api — pass to `createTestEnv(bot, { packs: [againTestPack()] })`
4
+ * so retry tests don't need to call `autoRetry(env.api)` by hand.
5
+ *
6
+ * @example
7
+ * const env = createTestEnv(bot, { packs: [againTestPack({ maxRetries: 2 })] });
8
+ * env.onApi("sendMessage", apiError(429, "retry after 0"), { times: 1 });
9
+ * await env.createUser().sendCommand("start"); // retries once, then succeeds
10
+ */
11
+ export function againTestPack(options) {
12
+ return {
13
+ name: "again",
14
+ setup(env) {
15
+ autoRetry(env.api, options);
16
+ },
17
+ };
18
+ }
19
+ //# sourceMappingURL=test-pack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-pack.js","sourceRoot":"","sources":["../src/test-pack.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9D;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,OAA0B;IACvD,OAAO;QACN,IAAI,EAAE,OAAO;QACb,KAAK,CAAC,GAAG;YACR,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC7B,CAAC;KACD,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaebal/again",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "yaebal auto-retry plugin — retries on 429/flood-wait and transient 5xx.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./lib/index.d.ts",
11
11
  "import": "./lib/index.js"
12
+ },
13
+ "./test-pack": {
14
+ "types": "./lib/test-pack.d.ts",
15
+ "import": "./lib/test-pack.js"
12
16
  }
13
17
  },
14
18
  "files": [
@@ -16,10 +20,11 @@
16
20
  "src"
17
21
  ],
18
22
  "dependencies": {
19
- "@yaebal/core": "0.0.5"
23
+ "@yaebal/core": "0.0.8"
20
24
  },
21
25
  "devDependencies": {
22
- "@types/node": "latest"
26
+ "@types/node": "latest",
27
+ "@yaebal/test": "0.2.0"
23
28
  },
24
29
  "engines": {
25
30
  "node": ">=20"
@@ -43,6 +48,6 @@
43
48
  "scripts": {
44
49
  "build": "tsc -p tsconfig.json",
45
50
  "typecheck": "tsc -p tsconfig.json --noEmit",
46
- "test": "node --test lib"
51
+ "test": "node --test lib/*.test.js"
47
52
  }
48
53
  }
package/src/index.test.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import type { Api, ErrorHook } from "@yaebal/core";
4
- import { TelegramError } from "@yaebal/core";
4
+ import { Composer, type Context, TelegramError } from "@yaebal/core";
5
+ import { apiError, createTestEnv } from "@yaebal/test";
5
6
  import { autoRetry, decideRetry } from "./index.js";
7
+ import { againTestPack } from "./test-pack.js";
6
8
 
7
9
  test("429 with retry_after waits exactly that long", () => {
8
10
  assert.deepEqual(
@@ -57,3 +59,27 @@ test("autoRetry can be installed as a bot plugin", () => {
57
59
 
58
60
  assert.equal(typeof hook, "function");
59
61
  });
62
+
63
+ test("againTestPack: a bot under test transparently retries a 429 and succeeds", async () => {
64
+ const bot = new Composer<Context>().on("message:text", (ctx) => ctx.reply("pong"));
65
+ const env = createTestEnv(bot, { packs: [againTestPack({ maxRetries: 2 })] });
66
+
67
+ env.onApi("sendMessage", apiError(429, "Too Many Requests: retry after 0"), { times: 1 });
68
+
69
+ await env.createUser().sendMessage("ping");
70
+
71
+ assert.equal(env.callsTo("sendMessage").length, 2); // first attempt failed, second succeeded
72
+ assert.equal(env.lastApiCall("sendMessage")?.params?.text, "pong");
73
+ });
74
+
75
+ test("againTestPack: exhausting maxRetries still throws", async () => {
76
+ const bot = new Composer<Context>().on("message:text", async (ctx) => {
77
+ await assert.rejects(ctx.reply("pong"), TelegramError);
78
+ });
79
+ const env = createTestEnv(bot, { packs: [againTestPack({ maxRetries: 1 })] });
80
+
81
+ env.onApi("sendMessage", apiError(429, "Too Many Requests: retry after 0"));
82
+
83
+ await env.createUser().sendMessage("ping");
84
+ assert.equal(env.callsTo("sendMessage").length, 2); // 1 initial attempt + 1 retry, then gives up
85
+ });
@@ -0,0 +1,20 @@
1
+ import type { TestPack } from "@yaebal/test";
2
+ import { type AutoRetryOptions, autoRetry } from "./index.js";
3
+
4
+ /**
5
+ * wires {@link autoRetry} onto a `TestEnv`'s mock api — pass to `createTestEnv(bot, { packs: [againTestPack()] })`
6
+ * so retry tests don't need to call `autoRetry(env.api)` by hand.
7
+ *
8
+ * @example
9
+ * const env = createTestEnv(bot, { packs: [againTestPack({ maxRetries: 2 })] });
10
+ * env.onApi("sendMessage", apiError(429, "retry after 0"), { times: 1 });
11
+ * await env.createUser().sendCommand("start"); // retries once, then succeeds
12
+ */
13
+ export function againTestPack(options?: AutoRetryOptions): TestPack {
14
+ return {
15
+ name: "again",
16
+ setup(env) {
17
+ autoRetry(env.api, options);
18
+ },
19
+ };
20
+ }