kubernetes-fluent-client 1.9.0 → 2.0.0

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/dist/index.d.ts CHANGED
@@ -4,10 +4,11 @@ import * as kind from "./upstream";
4
4
  export { kind };
5
5
  export { fetch } from "./fetch";
6
6
  export { StatusCodes as fetchStatus } from "http-status-codes";
7
+ export { WatchCfg, WatchEvent } from "./fluent/watch";
7
8
  export { K8s } from "./fluent";
8
9
  export { RegisterKind, modelToGroupVersionKind } from "./kinds";
9
10
  export { GenericKind } from "./types";
10
11
  export * from "./types";
11
- export * as K8sClientNode from "@kubernetes/client-node";
12
- export { fromEnv } from "./helpers";
12
+ export * as models from "@kubernetes/client-node/dist/gen/models/all";
13
+ export { fromEnv, waitForCluster } from "./helpers";
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,SAAS,CAAC;AAGjB,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAEnC,oGAAoG;AACpG,OAAO,EAAE,IAAI,EAAE,CAAC;AAGhB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,WAAW,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,cAAc,SAAS,CAAC;AAExB,OAAO,KAAK,aAAa,MAAM,yBAAyB,CAAC;AAEzD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,SAAS,CAAC;AAGjB,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAEnC,oGAAoG;AACpG,OAAO,EAAE,IAAI,EAAE,CAAC;AAGhB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,WAAW,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGtD,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,cAAc,SAAS,CAAC;AAGxB,OAAO,KAAK,MAAM,MAAM,6CAA6C,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
28
28
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
29
29
  };
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.fromEnv = exports.K8sClientNode = exports.GenericKind = exports.modelToGroupVersionKind = exports.RegisterKind = exports.K8s = exports.fetchStatus = exports.fetch = exports.kind = void 0;
31
+ exports.waitForCluster = exports.fromEnv = exports.models = exports.GenericKind = exports.modelToGroupVersionKind = exports.RegisterKind = exports.K8s = exports.WatchEvent = exports.fetchStatus = exports.fetch = exports.kind = void 0;
32
32
  require("./patch");
33
33
  // Export kinds as a single object
34
34
  const kind = __importStar(require("./upstream"));
@@ -39,6 +39,9 @@ Object.defineProperty(exports, "fetch", { enumerable: true, get: function () { r
39
39
  // Export the HTTP status codes
40
40
  var http_status_codes_1 = require("http-status-codes");
41
41
  Object.defineProperty(exports, "fetchStatus", { enumerable: true, get: function () { return http_status_codes_1.StatusCodes; } });
42
+ // Export the Watch Config and Event types
43
+ var watch_1 = require("./fluent/watch");
44
+ Object.defineProperty(exports, "WatchEvent", { enumerable: true, get: function () { return watch_1.WatchEvent; } });
42
45
  // Export the fluent API entrypoint
43
46
  var fluent_1 = require("./fluent");
44
47
  Object.defineProperty(exports, "K8s", { enumerable: true, get: function () { return fluent_1.K8s; } });
@@ -50,6 +53,8 @@ Object.defineProperty(exports, "modelToGroupVersionKind", { enumerable: true, ge
50
53
  var types_1 = require("./types");
51
54
  Object.defineProperty(exports, "GenericKind", { enumerable: true, get: function () { return types_1.GenericKind; } });
52
55
  __exportStar(require("./types"), exports);
53
- exports.K8sClientNode = __importStar(require("@kubernetes/client-node"));
56
+ // Export the upstream raw models
57
+ exports.models = __importStar(require("@kubernetes/client-node/dist/gen/models/all"));
54
58
  var helpers_1 = require("./helpers");
55
59
  Object.defineProperty(exports, "fromEnv", { enumerable: true, get: function () { return helpers_1.fromEnv; } });
60
+ Object.defineProperty(exports, "waitForCluster", { enumerable: true, get: function () { return helpers_1.waitForCluster; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubernetes-fluent-client",
3
- "version": "1.9.0",
3
+ "version": "2.0.0",
4
4
  "description": "A @kubernetes/client-node fluent API wrapper that leverages K8s Server Side Apply",
5
5
  "bin": "./dist/cli.js",
6
6
  "main": "dist/index.js",
@@ -35,13 +35,13 @@
35
35
  },
36
36
  "homepage": "https://github.com/defenseunicorns/kubernetes-fluent-client#readme",
37
37
  "dependencies": {
38
- "@kubernetes/client-node": "1.0.0-rc3",
38
+ "@kubernetes/client-node": "1.0.0-rc4",
39
39
  "byline": "5.0.0",
40
40
  "fast-json-patch": "3.1.1",
41
41
  "http-status-codes": "2.3.0",
42
42
  "node-fetch": "2.7.0",
43
43
  "quicktype-core": "23.0.80",
44
- "type-fest": "4.8.2",
44
+ "type-fest": "4.9.0",
45
45
  "yargs": "17.7.2"
46
46
  },
47
47
  "devDependencies": {
@@ -49,17 +49,17 @@
49
49
  "@commitlint/config-conventional": "18.4.3",
50
50
  "@jest/globals": "29.7.0",
51
51
  "@types/byline": "4.2.36",
52
- "@types/readable-stream": "4.0.9",
52
+ "@types/readable-stream": "4.0.10",
53
53
  "@types/yargs": "17.0.32",
54
- "@typescript-eslint/eslint-plugin": "6.13.1",
55
- "@typescript-eslint/parser": "6.13.1",
56
- "eslint-plugin-jsdoc": "46.9.0",
54
+ "@typescript-eslint/eslint-plugin": "6.15.0",
55
+ "@typescript-eslint/parser": "6.15.0",
56
+ "eslint-plugin-jsdoc": "46.9.1",
57
57
  "jest": "29.7.0",
58
58
  "nock": "13.4.0",
59
- "prettier": "3.1.0",
60
- "semantic-release": "22.0.8",
59
+ "prettier": "3.1.1",
60
+ "semantic-release": "22.0.12",
61
61
  "ts-jest": "29.1.1",
62
- "typescript": "5.3.2"
62
+ "typescript": "5.3.3"
63
63
  },
64
64
  "release": {
65
65
  "branches": [
@@ -1,12 +1,15 @@
1
1
  import { beforeEach, describe, expect, it, jest } from "@jest/globals";
2
+ import { V1APIGroup } from "@kubernetes/client-node";
2
3
  import { Operation } from "fast-json-patch";
3
4
 
4
- import { Pod } from "../upstream";
5
5
  import { K8s } from ".";
6
- import { k8sExec } from "./utils";
6
+ import { fetch } from "../fetch";
7
+ import { Pod } from "../upstream";
8
+ import { k8sCfg, k8sExec } from "./utils";
7
9
 
8
10
  // Setup mocks
9
11
  jest.mock("./utils");
12
+ jest.mock("../fetch");
10
13
 
11
14
  const generateFakePodManagedFields = (manager: string) => {
12
15
  return [
@@ -52,6 +55,7 @@ describe("Kube", () => {
52
55
  },
53
56
  };
54
57
 
58
+ const mockedKubeCfg = jest.mocked(k8sCfg);
55
59
  const mockedKubeExec = jest.mocked(k8sExec).mockResolvedValue(fakeResource);
56
60
 
57
61
  beforeEach(() => {
@@ -194,4 +198,35 @@ describe("Kube", () => {
194
198
  expect.objectContaining({ status: 500 }),
195
199
  );
196
200
  });
201
+
202
+ it("should create a raw api request", async () => {
203
+ mockedKubeCfg.mockReturnValue(
204
+ new Promise(r =>
205
+ r({
206
+ serverUrl: "http://localhost:8080",
207
+ opts: {},
208
+ }),
209
+ ),
210
+ );
211
+ const mockResp = {
212
+ kind: "APIVersions",
213
+ versions: ["v1"],
214
+ serverAddressByClientCIDRs: [
215
+ {
216
+ serverAddress: "172.27.0.3:6443",
217
+ },
218
+ ],
219
+ };
220
+
221
+ jest.mocked(fetch).mockResolvedValue({
222
+ ok: true,
223
+ data: mockResp,
224
+ status: 200,
225
+ statusText: "OK",
226
+ });
227
+
228
+ const result = await K8s(V1APIGroup).Raw("/api");
229
+
230
+ expect(result).toEqual(mockResp);
231
+ });
197
232
  });
@@ -6,12 +6,13 @@ import { Operation } from "fast-json-patch";
6
6
  import { StatusCodes } from "http-status-codes";
7
7
  import type { PartialDeep } from "type-fest";
8
8
 
9
+ import { fetch } from "../fetch";
9
10
  import { modelToGroupVersionKind } from "../kinds";
10
11
  import { GenericClass } from "../types";
11
12
  import { ApplyCfg } from "./apply";
12
13
  import { Filters, K8sInit, Paths, WatchAction } from "./types";
13
- import { k8sExec } from "./utils";
14
- import { ExecWatch, WatchCfg } from "./watch";
14
+ import { k8sCfg, k8sExec } from "./utils";
15
+ import { WatchCfg, Watcher } from "./watch";
15
16
 
16
17
  /**
17
18
  * Kubernetes fluent API inspired by Kubectl. Pass in a model, then call filters and actions on it.
@@ -23,7 +24,7 @@ import { ExecWatch, WatchCfg } from "./watch";
23
24
  export function K8s<T extends GenericClass, K extends KubernetesObject = InstanceType<T>>(
24
25
  model: T,
25
26
  filters: Filters = {},
26
- ): K8sInit<K> {
27
+ ): K8sInit<T, K> {
27
28
  const withFilters = { WithField, WithLabel, Get, Delete, Watch };
28
29
  const matchedKind = filters.kindOverride || modelToGroupVersionKind(model.name);
29
30
 
@@ -158,16 +159,32 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
158
159
  throw new Error("No operations specified");
159
160
  }
160
161
 
161
- return k8sExec<T, K>(model, filters, "PATCH", payload);
162
+ return k8sExec(model, filters, "PATCH", payload);
162
163
  }
163
164
 
164
165
  /**
165
166
  * @inheritdoc
166
167
  * @see {@link K8sInit.Watch}
167
168
  */
168
- async function Watch(callback: WatchAction<T>, watchCfg?: WatchCfg) {
169
- return ExecWatch(model, filters, callback, watchCfg);
169
+ function Watch(callback: WatchAction<T>, watchCfg?: WatchCfg) {
170
+ return new Watcher(model, filters, callback, watchCfg);
170
171
  }
171
172
 
172
- return { InNamespace, Apply, Create, Patch, ...withFilters };
173
+ /**
174
+ * @inheritdoc
175
+ * @see {@link K8sInit.Raw}
176
+ */
177
+ async function Raw(url: string) {
178
+ const thing = await k8sCfg("GET");
179
+ const { opts, serverUrl } = thing;
180
+ const resp = await fetch<K>(`${serverUrl}${url}`, opts);
181
+
182
+ if (resp.ok) {
183
+ return resp.data;
184
+ }
185
+
186
+ throw resp;
187
+ }
188
+
189
+ return { InNamespace, Apply, Create, Patch, Raw, ...withFilters };
173
190
  }
@@ -6,8 +6,8 @@ import { Operation } from "fast-json-patch";
6
6
  import type { PartialDeep } from "type-fest";
7
7
 
8
8
  import { GenericClass, GroupVersionKind } from "../types";
9
- import { WatchCfg, WatchController } from "./watch";
10
9
  import { ApplyCfg } from "./apply";
10
+ import { WatchCfg, Watcher } from "./watch";
11
11
 
12
12
  /**
13
13
  * The Phase matched when using the K8s Watch API.
@@ -16,6 +16,8 @@ export enum WatchPhase {
16
16
  Added = "ADDED",
17
17
  Modified = "MODIFIED",
18
18
  Deleted = "DELETED",
19
+ Bookmark = "BOOKMARK",
20
+ Error = "ERROR",
19
21
  }
20
22
 
21
23
  export type FetchMethods = "GET" | "APPLY" | "POST" | "PUT" | "DELETE" | "PATCH" | "WATCH";
@@ -41,7 +43,7 @@ export type GetFunction<K extends KubernetesObject> = {
41
43
  (name: string): Promise<K>;
42
44
  };
43
45
 
44
- export type K8sFilteredActions<K extends KubernetesObject> = {
46
+ export type K8sFilteredActions<T extends GenericClass, K extends KubernetesObject> = {
45
47
  /**
46
48
  * Get the resource or resources matching the filters.
47
49
  * If no filters are specified, all resources will be returned.
@@ -63,10 +65,7 @@ export type K8sFilteredActions<K extends KubernetesObject> = {
63
65
  * @param watchCfg - (optional) watch configuration
64
66
  * @returns a watch controller
65
67
  */
66
- Watch: (
67
- callback: (payload: K, phase: WatchPhase) => void,
68
- watchCfg?: WatchCfg,
69
- ) => Promise<WatchController>;
68
+ Watch: (callback: WatchAction<T>, watchCfg?: WatchCfg) => Watcher<T>;
70
69
  };
71
70
 
72
71
  export type K8sUnfilteredActions<K extends KubernetesObject> = {
@@ -96,9 +95,31 @@ export type K8sUnfilteredActions<K extends KubernetesObject> = {
96
95
  * @returns The patched resource
97
96
  */
98
97
  Patch: (payload: Operation[]) => Promise<K>;
98
+
99
+ /**
100
+ * Perform a raw GET request to the Kubernetes API. This is useful for calling endpoints that are not supported by the fluent API.
101
+ * This command mirrors the `kubectl get --raw` command.
102
+ *
103
+ * E.g.
104
+ *
105
+ * ```ts
106
+ * import { V1APIGroup } from "@kubernetes/client-node";
107
+ *
108
+ * K8s(V1APIGroup).Raw("/api")
109
+ * ```
110
+ *
111
+ * will call the `/api` endpoint and is equivalent to `kubectl get --raw /api`.
112
+ *
113
+ * @param url the URL to call (e.g. /api)
114
+ * @returns
115
+ */
116
+ Raw: (url: string) => Promise<K>;
99
117
  };
100
118
 
101
- export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> & {
119
+ export type K8sWithFilters<T extends GenericClass, K extends KubernetesObject> = K8sFilteredActions<
120
+ T,
121
+ K
122
+ > & {
102
123
  /**
103
124
  * Filter the query by the given field.
104
125
  * Note multiple calls to this method will result in an AND condition. e.g.
@@ -118,7 +139,7 @@ export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> &
118
139
  * @param value - the field value
119
140
  * @returns the fluent API
120
141
  */
121
- WithField: <P extends Paths<K>>(key: P, value: string) => K8sWithFilters<K>;
142
+ WithField: <P extends Paths<K>>(key: P, value: string) => K8sWithFilters<T, K>;
122
143
 
123
144
  /**
124
145
  * Filter the query by the given label. If no value is specified, the label simply must exist.
@@ -138,10 +159,10 @@ export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> &
138
159
  * @param value - the label value
139
160
  * @returns the fluent API
140
161
  */
141
- WithLabel: (key: string, value?: string) => K8sWithFilters<K>;
162
+ WithLabel: (key: string, value?: string) => K8sWithFilters<T, K>;
142
163
  };
143
164
 
144
- export type K8sInit<K extends KubernetesObject> = K8sWithFilters<K> &
165
+ export type K8sInit<T extends GenericClass, K extends KubernetesObject> = K8sWithFilters<T, K> &
145
166
  K8sUnfilteredActions<K> & {
146
167
  /**
147
168
  * Set the namespace filter.
@@ -149,7 +170,7 @@ export type K8sInit<K extends KubernetesObject> = K8sWithFilters<K> &
149
170
  * @param namespace - the namespace to filter on
150
171
  * @returns the fluent API
151
172
  */
152
- InNamespace: (namespace: string) => K8sWithFilters<K>;
173
+ InNamespace: (namespace: string) => K8sWithFilters<T, K>;
153
174
  };
154
175
 
155
176
  export type WatchAction<T extends GenericClass, K extends KubernetesObject = InstanceType<T>> = (
@@ -0,0 +1,264 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
4
+ import nock from "nock";
5
+ import { PassThrough } from "readable-stream";
6
+
7
+ import { K8s } from ".";
8
+ import { WatchEvent, kind } from "..";
9
+ import { WatchPhase } from "./types";
10
+ import { Watcher } from "./watch";
11
+
12
+ describe("Watcher", () => {
13
+ const evtMock = jest.fn<(update: kind.Pod, phase: WatchPhase) => void>();
14
+ const errMock = jest.fn<(err: Error) => void>();
15
+
16
+ const setupAndStartWatcher = (eventType: WatchEvent, handler: (...args: any[]) => void) => {
17
+ watcher.events.on(eventType, handler);
18
+ watcher.start().catch(errMock);
19
+ };
20
+
21
+ let watcher: Watcher<typeof kind.Pod>;
22
+
23
+ beforeEach(() => {
24
+ jest.resetAllMocks();
25
+ watcher = K8s(kind.Pod).Watch(evtMock, {
26
+ retryDelaySec: 1,
27
+ });
28
+
29
+ nock("http://jest-test:8080")
30
+ .get("/api/v1/pods")
31
+ .query({ watch: "true", allowWatchBookmarks: "true" })
32
+ .reply(200, () => {
33
+ const stream = new PassThrough();
34
+
35
+ const resources = [
36
+ { type: "ADDED", object: createMockPod(`pod-0`, `1`) },
37
+ { type: "BOOKMARK", object: { metadata: { resourceVersion: "1" } } },
38
+ { type: "MODIFIED", object: createMockPod(`pod-0`, `2`) },
39
+ ];
40
+
41
+ resources.forEach(resource => {
42
+ stream.write(JSON.stringify(resource) + "\n");
43
+ });
44
+
45
+ stream.end();
46
+
47
+ return stream;
48
+ });
49
+ });
50
+
51
+ afterEach(() => {
52
+ watcher.close();
53
+ });
54
+
55
+ it("should watch named resources", done => {
56
+ nock.cleanAll();
57
+ nock("http://jest-test:8080")
58
+ .get("/api/v1/namespaces/tester/pods")
59
+ .query({ watch: "true", allowWatchBookmarks: "true", fieldSelector: "metadata.name=demo" })
60
+ .reply(200);
61
+
62
+ watcher = K8s(kind.Pod, { name: "demo" }).InNamespace("tester").Watch(evtMock);
63
+
64
+ setupAndStartWatcher(WatchEvent.CONNECT, () => {
65
+ done();
66
+ });
67
+ });
68
+
69
+ it("should start the watch at the specified resource version", done => {
70
+ nock.cleanAll();
71
+ nock("http://jest-test:8080")
72
+ .get("/api/v1/pods")
73
+ .query({
74
+ watch: "true",
75
+ allowWatchBookmarks: "true",
76
+ resourceVersion: "25",
77
+ })
78
+ .reply(200);
79
+
80
+ watcher = K8s(kind.Pod).Watch(evtMock, {
81
+ resourceVersion: "25",
82
+ });
83
+
84
+ setupAndStartWatcher(WatchEvent.CONNECT, () => {
85
+ done();
86
+ });
87
+ });
88
+
89
+ it("should handle resource version is too old", done => {
90
+ nock.cleanAll();
91
+ nock("http://jest-test:8080")
92
+ .get("/api/v1/pods")
93
+ .query({ watch: "true", allowWatchBookmarks: "true", resourceVersion: "45" })
94
+ .reply(200, () => {
95
+ const stream = new PassThrough();
96
+ stream.write(
97
+ JSON.stringify({
98
+ type: "ERROR",
99
+ object: {
100
+ kind: "Status",
101
+ apiVersion: "v1",
102
+ metadata: {},
103
+ status: "Failure",
104
+ message: "too old resource version: 123 (391079)",
105
+ reason: "Gone",
106
+ code: 410,
107
+ },
108
+ }) + "\n",
109
+ );
110
+
111
+ stream.end();
112
+ return stream;
113
+ });
114
+
115
+ watcher = K8s(kind.Pod).Watch(evtMock, {
116
+ resourceVersion: "45",
117
+ });
118
+
119
+ setupAndStartWatcher(WatchEvent.OLD_RESOURCE_VERSION, res => {
120
+ expect(res).toEqual("45");
121
+ done();
122
+ });
123
+ });
124
+
125
+ it("should call the event handler for each event", done => {
126
+ watcher = K8s(kind.Pod).Watch((evt, phase) => {
127
+ expect(evt.metadata?.name).toEqual(`pod-0`);
128
+ expect(phase).toEqual(WatchPhase.Added);
129
+ done();
130
+ });
131
+
132
+ watcher.start().catch(errMock);
133
+ });
134
+
135
+ it("should return the cache id", done => {
136
+ watcher
137
+ .start()
138
+ .then(() => {
139
+ expect(watcher.id).toEqual("d69b75a611");
140
+ done();
141
+ })
142
+ .catch(errMock);
143
+ });
144
+
145
+ it("should handle calling .id() before .start()", () => {
146
+ expect(() => watcher.id).toThrowError("watch not started");
147
+ });
148
+
149
+ it("should handle the CONNECT event", done => {
150
+ setupAndStartWatcher(WatchEvent.CONNECT, () => {
151
+ done();
152
+ });
153
+ });
154
+
155
+ it("should handle the DATA event", done => {
156
+ setupAndStartWatcher(WatchEvent.DATA, (pod, phase) => {
157
+ expect(pod.metadata?.name).toEqual(`pod-0`);
158
+ expect(phase).toEqual(WatchPhase.Added);
159
+ done();
160
+ });
161
+ });
162
+
163
+ it("should handle the BOOKMARK event", done => {
164
+ setupAndStartWatcher(WatchEvent.BOOKMARK, bookmark => {
165
+ expect(bookmark.metadata?.resourceVersion).toEqual("1");
166
+ done();
167
+ });
168
+ });
169
+
170
+ it("should handle the NETWORK_ERROR event", done => {
171
+ nock.cleanAll();
172
+ nock("http://jest-test:8080")
173
+ .get("/api/v1/pods")
174
+ .query({ watch: "true", allowWatchBookmarks: "true" })
175
+ .replyWithError("Something bad happened");
176
+
177
+ setupAndStartWatcher(WatchEvent.NETWORK_ERROR, error => {
178
+ expect(error.message).toEqual(
179
+ "request to http://jest-test:8080/api/v1/pods?watch=true&allowWatchBookmarks=true failed, reason: Something bad happened",
180
+ );
181
+ done();
182
+ });
183
+ });
184
+
185
+ it("should handle the RECONNECT event", done => {
186
+ nock.cleanAll();
187
+ nock("http://jest-test:8080")
188
+ .get("/api/v1/pods")
189
+ .query({ watch: "true", allowWatchBookmarks: "true" })
190
+ .replyWithError("Something bad happened");
191
+
192
+ setupAndStartWatcher(WatchEvent.RECONNECT, error => {
193
+ expect(error.message).toEqual(
194
+ "request to http://jest-test:8080/api/v1/pods?watch=true&allowWatchBookmarks=true failed, reason: Something bad happened",
195
+ );
196
+ done();
197
+ });
198
+ });
199
+
200
+ it("should perform a resync after the resync interval", done => {
201
+ watcher = K8s(kind.Pod).Watch(evtMock, {
202
+ resyncIntervalSec: 1,
203
+ });
204
+
205
+ setupAndStartWatcher(WatchEvent.RESYNC, err => {
206
+ expect(err.name).toEqual("Resync");
207
+ expect(err.message).toEqual("Resync triggered by resyncIntervalSec");
208
+ done();
209
+ });
210
+ });
211
+
212
+ it("should handle the GIVE_UP event", done => {
213
+ nock.cleanAll();
214
+ nock("http://jest-test:8080");
215
+
216
+ watcher = K8s(kind.Pod).Watch(evtMock, {
217
+ retryMax: 1,
218
+ retryDelaySec: 1,
219
+ });
220
+
221
+ setupAndStartWatcher(WatchEvent.GIVE_UP, error => {
222
+ expect(error.message).toContain(
223
+ "request to http://jest-test:8080/api/v1/pods?watch=true&allowWatchBookmarks=true failed",
224
+ );
225
+ done();
226
+ });
227
+ });
228
+ });
229
+
230
+ /**
231
+ * Creates a mock pod object
232
+ *
233
+ * @param name The name of the pod
234
+ * @param resourceVersion The resource version of the pod
235
+ * @returns A mock pod object
236
+ */
237
+ function createMockPod(name: string, resourceVersion: string): kind.Pod {
238
+ return {
239
+ kind: "Pod",
240
+ apiVersion: "v1",
241
+ metadata: {
242
+ name: name,
243
+ resourceVersion: resourceVersion,
244
+ // ... other metadata fields
245
+ },
246
+ spec: {
247
+ containers: [
248
+ {
249
+ name: "nginx",
250
+ image: "nginx:1.14.2",
251
+ ports: [
252
+ {
253
+ containerPort: 80,
254
+ protocol: "TCP",
255
+ },
256
+ ],
257
+ },
258
+ ],
259
+ },
260
+ status: {
261
+ // ... pod status
262
+ },
263
+ };
264
+ }