kubernetes-fluent-client 3.0.3 → 4.0.0-rc-http2-watch

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.
Files changed (42) hide show
  1. package/.prettierignore +4 -0
  2. package/README.md +24 -0
  3. package/dist/cli.js +21 -1
  4. package/dist/fileSystem.d.ts +11 -0
  5. package/dist/fileSystem.d.ts.map +1 -0
  6. package/dist/fileSystem.js +42 -0
  7. package/dist/fileSystem.test.d.ts +2 -0
  8. package/dist/fileSystem.test.d.ts.map +1 -0
  9. package/dist/fileSystem.test.js +75 -0
  10. package/dist/fluent/watch.d.ts +2 -0
  11. package/dist/fluent/watch.d.ts.map +1 -1
  12. package/dist/fluent/watch.js +147 -27
  13. package/dist/generate.d.ts +71 -11
  14. package/dist/generate.d.ts.map +1 -1
  15. package/dist/generate.js +130 -117
  16. package/dist/generate.test.js +293 -346
  17. package/dist/postProcessing.d.ts +246 -0
  18. package/dist/postProcessing.d.ts.map +1 -0
  19. package/dist/postProcessing.js +497 -0
  20. package/dist/postProcessing.test.d.ts +2 -0
  21. package/dist/postProcessing.test.d.ts.map +1 -0
  22. package/dist/postProcessing.test.js +550 -0
  23. package/e2e/cli.e2e.test.ts +127 -0
  24. package/e2e/crds/policyreports.default.expected/policyreport-v1alpha1.ts +332 -0
  25. package/e2e/crds/policyreports.default.expected/policyreport-v1alpha2.ts +360 -0
  26. package/e2e/crds/policyreports.default.expected/policyreport-v1beta1.ts +360 -0
  27. package/e2e/crds/policyreports.no.post.expected/policyreport-v1alpha1.ts +331 -0
  28. package/e2e/crds/policyreports.no.post.expected/policyreport-v1alpha2.ts +360 -0
  29. package/e2e/crds/policyreports.no.post.expected/policyreport-v1beta1.ts +360 -0
  30. package/e2e/crds/test.yaml/policyreports.test.yaml +1008 -0
  31. package/e2e/crds/test.yaml/uds-podmonitors.test.yaml +1245 -0
  32. package/e2e/crds/uds-podmonitors.default.expected/podmonitor-v1.ts +1333 -0
  33. package/e2e/crds/uds-podmonitors.no.post.expected/podmonitor-v1.ts +1360 -0
  34. package/package.json +6 -5
  35. package/src/cli.ts +25 -1
  36. package/src/fileSystem.test.ts +67 -0
  37. package/src/fileSystem.ts +25 -0
  38. package/src/fluent/watch.ts +174 -35
  39. package/src/generate.test.ts +368 -358
  40. package/src/generate.ts +173 -154
  41. package/src/postProcessing.test.ts +742 -0
  42. package/src/postProcessing.ts +568 -0
@@ -1,389 +1,399 @@
1
1
  import { beforeEach, describe, expect, jest, test } from "@jest/globals";
2
- import { generate } from "./generate";
2
+ import { convertCRDtoTS, GenerateOptions, readOrFetchCrd } from "./generate";
3
3
  import fs from "fs";
4
+ import path from "path";
5
+ import { quicktype } from "quicktype-core";
6
+ import { fetch } from "./fetch";
7
+ import { loadAllYaml } from "@kubernetes/client-node";
8
+ import { K8s } from "./fluent";
9
+ import { CustomResourceDefinition } from "./upstream";
4
10
 
5
- const sampleYaml = `
6
- # non-crd should be ignored
7
- apiVersion: v1
8
- kind: ConfigMap
9
- metadata:
10
- name: test
11
- namespace: default
12
- data:
13
- any: bleh
14
- ---
15
- apiVersion: apiextensions.k8s.io/v1
16
- kind: CustomResourceDefinition
17
- metadata:
18
- name: movies.example.com
19
- spec:
20
- group: example.com
21
- names:
22
- kind: Movie
23
- plural: movies
24
- scope: Namespaced
25
- versions:
26
- - name: v1
27
- schema:
28
- openAPIV3Schema:
29
- type: object
30
- description: Movie nerd
31
- properties:
32
- spec:
33
- properties:
34
- title:
35
- type: string
36
- author:
37
- type: string
38
- type: object
39
- ---
40
- # duplicate entries should not break things
41
- apiVersion: apiextensions.k8s.io/v1
42
- kind: CustomResourceDefinition
43
- metadata:
44
- name: movies.example.com
45
- spec:
46
- group: example.com
47
- names:
48
- kind: Movie
49
- plural: movies
50
- scope: Namespaced
51
- versions:
52
- - name: v1
53
- schema:
54
- openAPIV3Schema:
55
- type: object
56
- description: Movie nerd
57
- properties:
58
- spec:
59
- properties:
60
- title:
61
- type: string
62
- author:
63
- type: string
64
- type: object
65
- ---
66
- # should support multiple versions
67
- apiVersion: apiextensions.k8s.io/v1
68
- kind: CustomResourceDefinition
69
- metadata:
70
- name: books.example.com
71
- spec:
72
- group: example.com
73
- names:
74
- kind: Book
75
- plural: books
76
- scope: Namespaced
77
- versions:
78
- - name: v1
79
- schema:
80
- openAPIV3Schema:
81
- type: object
82
- description: Book nerd
83
- properties:
84
- spec:
85
- properties:
86
- title:
87
- type: string
88
- author:
89
- type: string
90
- type: object
91
- - name: v2
92
- schema:
93
- openAPIV3Schema:
94
- type: object
95
- description: Book nerd
96
- properties:
97
- spec:
98
- properties:
99
- author:
100
- type: string
101
- type: object
102
- served: true
103
- storage: true
104
- `;
105
-
106
- jest.mock("./fetch", () => ({
107
- fetch: jest.fn(),
11
+ // Mock the file system
12
+ jest.mock("fs", () => ({
13
+ ...(jest.requireActual("fs") as object), // Preserve the rest of the fs module
14
+ writeFileSync: jest.fn(), // Mock only writeFileSync
15
+ existsSync: jest.fn(),
16
+ readFileSync: jest.fn(),
108
17
  }));
109
-
18
+ jest.mock("./fetch");
19
+ jest.mock("quicktype-core", () => {
20
+ const actualQuicktypeCore = jest.requireActual<typeof import("quicktype-core")>("quicktype-core");
21
+ return {
22
+ quicktype: jest.fn(),
23
+ JSONSchemaInput: actualQuicktypeCore.JSONSchemaInput,
24
+ FetchingJSONSchemaStore: actualQuicktypeCore.FetchingJSONSchemaStore,
25
+ InputData: actualQuicktypeCore.InputData,
26
+ };
27
+ });
28
+ jest.mock("@kubernetes/client-node", () => {
29
+ const actualModule = jest.requireActual("@kubernetes/client-node");
30
+ return {
31
+ ...(typeof actualModule === "object" ? actualModule : {}),
32
+ loadAllYaml: jest.fn(), // Mock only the specific method
33
+ };
34
+ });
110
35
  jest.mock("./fluent", () => ({
111
36
  K8s: jest.fn(),
112
37
  }));
38
+ jest.mock("./generate", () => {
39
+ const actualGenerate = jest.requireActual("./generate");
40
+ return {
41
+ ...(typeof actualGenerate === "object" ? actualGenerate : {}),
42
+ resolveFilePath: jest.fn(), // Mock resolveFilePath globally
43
+ tryParseUrl: jest.fn(),
44
+ };
45
+ });
46
+
47
+ // Sample CRD content to use in tests
48
+ const sampleCrd = {
49
+ apiVersion: "apiextensions.k8s.io/v1",
50
+ kind: "CustomResourceDefinition",
51
+ metadata: { name: "movies.example.com" },
52
+ spec: {
53
+ group: "example.com",
54
+ names: { kind: "Movie", plural: "movies" },
55
+ scope: "Namespaced",
56
+ versions: [
57
+ {
58
+ name: "v1",
59
+ served: true,
60
+ storage: true,
61
+ schema: {
62
+ openAPIV3Schema: {
63
+ type: "object",
64
+ description: "Movie nerd",
65
+ properties: {
66
+ spec: {
67
+ properties: {
68
+ title: { type: "string" },
69
+ author: { type: "string" },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ },
75
+ },
76
+ ],
77
+ },
78
+ };
79
+
80
+ const expectedMovie = [
81
+ "/**",
82
+ " * Movie nerd",
83
+ " */",
84
+ "export interface Movie {",
85
+ " spec?: any[] | boolean | number | number | null | SpecObject | string;",
86
+ " [property: string]: any;",
87
+ "}",
88
+ "",
89
+ "export interface SpecObject {",
90
+ " author?: string;",
91
+ " title?: string;",
92
+ " [property: string]: any;",
93
+ "}",
94
+ "",
95
+ ];
113
96
 
114
97
  describe("CRD Generate", () => {
115
- const originalReadFileSync = fs.readFileSync;
116
-
117
- jest.spyOn(fs, "existsSync").mockReturnValue(true);
118
- jest.spyOn(fs, "readFileSync").mockImplementation((...args) => {
119
- // Super janky hack due ot source-map-support calling readFileSync internally
120
- if (args[0].toString().includes("test-crd.yaml")) {
121
- return sampleYaml;
122
- }
123
- return originalReadFileSync(...args);
98
+ let logFn: jest.Mock; // Mock log function
99
+
100
+ beforeEach(() => {
101
+ jest.clearAllMocks(); // Reset all mocks before each test
102
+ logFn = jest.fn(); // Mock the log function with correct typing
103
+ });
104
+
105
+ test("convertCRDtoTS should generate the expected TypeScript file", async () => {
106
+ // Mock convertCRDtoTS to return a valid result structure
107
+ (quicktype as jest.MockedFunction<typeof quicktype>).mockResolvedValueOnce({
108
+ lines: expectedMovie,
109
+ annotations: [],
110
+ });
111
+
112
+ const options = {
113
+ source: "test-crd.yaml",
114
+ language: "ts",
115
+ logFn,
116
+ directory: "test-dir",
117
+ plain: false,
118
+ npmPackage: "kubernetes-fluent-client",
119
+ };
120
+
121
+ // Call convertCRDtoTS with sample CRD
122
+ const result = await convertCRDtoTS(sampleCrd, options);
123
+
124
+ // Extract the generated types from the result
125
+ const generatedTypes = result[0].results["movie-v1"];
126
+
127
+ // Assert that the generated types match the expected TypeScript code
128
+ expect(generatedTypes).toEqual(expectedMovie);
129
+
130
+ // Assert the file writing happens with the expected TypeScript content
131
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
132
+ path.join("test-dir", "movie-v1.ts"),
133
+ expectedMovie.join("\n"),
134
+ );
135
+
136
+ // Assert the logs contain expected log messages
137
+ expect(logFn).toHaveBeenCalledWith("- Generating example.com/v1 types for Movie");
124
138
  });
125
- const mkdirSyncSpy = jest.spyOn(fs, "mkdirSync").mockReturnValue(undefined);
126
- const writeFileSyncSpy = jest.spyOn(fs, "writeFileSync").mockReturnValue(undefined);
139
+ });
140
+
141
+ describe("readOrFetchCrd", () => {
142
+ let mockOpts: GenerateOptions;
127
143
 
128
144
  beforeEach(() => {
129
145
  jest.clearAllMocks();
146
+ mockOpts = {
147
+ source: "mock-file-path",
148
+ logFn: jest.fn(),
149
+ };
150
+
151
+ // Reapply mock for resolveFilePath inside beforeEach
152
+ const { resolveFilePath } = jest.requireMock("./generate") as { resolveFilePath: jest.Mock };
153
+ resolveFilePath.mockReturnValue("mock-file-path");
130
154
  });
131
155
 
132
- test("converts CRD to TypeScript", async () => {
133
- const options = { source: "test-crd.yaml", language: "ts", logFn: jest.fn() };
134
-
135
- const actual = await generate(options);
136
- const expectedMovie = [
137
- "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
138
- 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
139
- "/**",
140
- " * Movie nerd",
141
- " */",
142
- "export class Movie extends GenericKind {",
143
- " spec?: Spec;",
144
- "}",
145
- "",
146
- "export interface Spec {",
147
- " author?: string;",
148
- " title?: string;",
149
- "}",
150
- "",
151
- "RegisterKind(Movie, {",
152
- ' group: "example.com",',
153
- ' version: "v1",',
154
- ' kind: "Movie",',
155
- ' plural: "movies",',
156
- "});",
157
- ];
158
- const expectedBookV1 = [
159
- "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
160
- 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
161
- "/**",
162
- " * Book nerd",
163
- " */",
164
- "export class Book extends GenericKind {",
165
- " spec?: Spec;",
166
- "}",
167
- "",
168
- "export interface Spec {",
169
- " author?: string;",
170
- " title?: string;",
171
- "}",
172
- "",
173
- "RegisterKind(Book, {",
174
- ' group: "example.com",',
175
- ' version: "v1",',
176
- ' kind: "Book",',
177
- ' plural: "books",',
178
- "});",
179
- ];
180
- const expectedBookV2 = expectedBookV1
181
- .filter(line => !line.includes("title?"))
182
- .map(line => line.replace("v1", "v2"));
183
-
184
- expect(actual["movie-v1"]).toEqual(expectedMovie);
185
- expect(actual["book-v1"]).toEqual(expectedBookV1);
186
- expect(actual["book-v2"]).toEqual(expectedBookV2);
156
+ test("should load CRD from a local file", async () => {
157
+ // Inside the test:
158
+ const absoluteFilePath = path.join(process.cwd(), "mock-file-path");
159
+
160
+ // Mock file system functions
161
+ (fs.existsSync as jest.Mock).mockReturnValue(true);
162
+ (fs.readFileSync as jest.Mock).mockReturnValue("mock file content");
163
+
164
+ // Mock loadAllYaml to return parsed CRD
165
+ const mockCrd = [{ kind: "CustomResourceDefinition" }] as CustomResourceDefinition[];
166
+ (loadAllYaml as jest.Mock).mockReturnValue(mockCrd);
167
+
168
+ // Call the function
169
+ const result = await readOrFetchCrd(mockOpts);
170
+
171
+ // Assert fs and loadAllYaml were called with correct args
172
+ expect(fs.existsSync).toHaveBeenCalledWith(absoluteFilePath);
173
+ expect(fs.readFileSync).toHaveBeenCalledWith(absoluteFilePath, "utf8");
174
+ expect(loadAllYaml).toHaveBeenCalledWith("mock file content");
175
+
176
+ // Assert the result matches the mocked CRD
177
+ expect(result).toEqual(mockCrd);
178
+
179
+ // Assert log function was called with correct message
180
+ expect(mockOpts.logFn).toHaveBeenCalledWith(
181
+ "Attempting to load mock-file-path as a local file",
182
+ );
187
183
  });
184
+ });
185
+
186
+ describe("readOrFetchCrd with URL", () => {
187
+ let mockOpts: GenerateOptions;
188
188
 
189
- test("converts CRD to TypeScript with plain option", async () => {
190
- const options = { source: "test-crd.yaml", language: "ts", plain: true, logFn: jest.fn() };
191
-
192
- const actual = await generate(options);
193
- const expectedMovie = [
194
- "/**",
195
- " * Movie nerd",
196
- " */",
197
- "export interface Movie {",
198
- " spec?: Spec;",
199
- "}",
200
- "",
201
- "export interface Spec {",
202
- " author?: string;",
203
- " title?: string;",
204
- "}",
205
- "",
206
- ];
207
- const expectedBookV1 = [
208
- "/**",
209
- " * Book nerd",
210
- " */",
211
- "export interface Book {",
212
- " spec?: Spec;",
213
- "}",
214
- "",
215
- "export interface Spec {",
216
- " author?: string;",
217
- " title?: string;",
218
- "}",
219
- "",
220
- ];
221
- const expectedBookV2 = expectedBookV1
222
- .filter(line => !line.includes("title?"))
223
- .map(line => line.replace("v1", "v2"));
224
-
225
- expect(actual["movie-v1"]).toEqual(expectedMovie);
226
- expect(actual["book-v1"]).toEqual(expectedBookV1);
227
- expect(actual["book-v2"]).toEqual(expectedBookV2);
189
+ beforeEach(() => {
190
+ jest.clearAllMocks();
191
+ mockOpts = {
192
+ source: "http://example.com/mock-crd",
193
+ logFn: jest.fn(),
194
+ };
195
+
196
+ // Mock resolveFilePath to simulate URL logic
197
+ const { resolveFilePath } = jest.requireMock("./generate") as {
198
+ resolveFilePath: jest.Mock;
199
+ };
200
+ resolveFilePath.mockReturnValue("mock-file-path");
201
+
202
+ // Ensure fs.existsSync returns false for URL tests to skip file logic
203
+ (fs.existsSync as jest.Mock).mockReturnValue(false);
228
204
  });
229
205
 
230
- test("converts CRD to TypeScript with other options", async () => {
231
- const options = {
232
- source: "test-crd.yaml",
233
- npmPackage: "test-package",
206
+ test("should fetch CRD from a URL and parse YAML", async () => {
207
+ const { tryParseUrl } = jest.requireMock("./generate") as { tryParseUrl: jest.Mock };
208
+ tryParseUrl.mockReturnValue(new URL("http://example.com/mock-crd"));
209
+
210
+ // Mock fetch to return a valid response
211
+ (fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue({
212
+ ok: true,
213
+ data: "mock fetched data",
214
+ status: 0,
215
+ statusText: "",
216
+ });
217
+
218
+ // Mock loadAllYaml to return parsed CRD
219
+ const mockCrd = [{ kind: "CustomResourceDefinition" }] as CustomResourceDefinition[];
220
+ (loadAllYaml as jest.Mock).mockReturnValue(mockCrd);
221
+
222
+ // Call the function
223
+ const result = await readOrFetchCrd(mockOpts);
224
+
225
+ // Assert fetch was called with correct URL
226
+ expect(fetch).toHaveBeenCalledWith("http://example.com/mock-crd");
227
+
228
+ // Assert loadAllYaml was called with fetched data
229
+ expect(loadAllYaml).toHaveBeenCalledWith("mock fetched data");
230
+
231
+ // Assert the result matches the mocked CRD
232
+ expect(result).toEqual(mockCrd);
233
+
234
+ // Assert log function was called with correct message
235
+ expect(mockOpts.logFn).toHaveBeenCalledWith(
236
+ "Attempting to load http://example.com/mock-crd as a URL",
237
+ );
238
+ });
239
+ });
240
+
241
+ describe("readOrFetchCrd from Kubernetes cluster", () => {
242
+ let mockOpts: GenerateOptions;
243
+
244
+ beforeEach(() => {
245
+ jest.clearAllMocks();
246
+ mockOpts = {
247
+ source: "my-crd",
234
248
  logFn: jest.fn(),
235
249
  };
236
250
 
237
- const actual = await generate(options);
238
- const expectedMovie = [
239
- "// This file is auto-generated by test-package, do not edit manually\n",
240
- 'import { GenericKind, RegisterKind } from "test-package";\n',
241
- "/**",
242
- " * Movie nerd",
243
- " */",
244
- "export class Movie extends GenericKind {",
245
- " spec?: Spec;",
246
- "}",
247
- "",
248
- "export interface Spec {",
249
- " author?: string;",
250
- " title?: string;",
251
- "}",
252
- "",
253
- "RegisterKind(Movie, {",
254
- ' group: "example.com",',
255
- ' version: "v1",',
256
- ' kind: "Movie",',
257
- ' plural: "movies",',
258
- "});",
259
- ];
260
- const expectedBookV1 = [
261
- "// This file is auto-generated by test-package, do not edit manually\n",
262
- 'import { GenericKind, RegisterKind } from "test-package";\n',
263
- "/**",
264
- " * Book nerd",
265
- " */",
266
- "export class Book extends GenericKind {",
267
- " spec?: Spec;",
268
- "}",
269
- "",
270
- "export interface Spec {",
271
- " author?: string;",
272
- " title?: string;",
273
- "}",
274
- "",
275
- "RegisterKind(Book, {",
276
- ' group: "example.com",',
277
- ' version: "v1",',
278
- ' kind: "Book",',
279
- ' plural: "books",',
280
- "});",
281
- ];
282
- const expectedBookV2 = expectedBookV1
283
- .filter(line => !line.includes("title?"))
284
- .map(line => line.replace("v1", "v2"));
285
-
286
- expect(actual["movie-v1"]).toEqual(expectedMovie);
287
- expect(actual["book-v1"]).toEqual(expectedBookV1);
288
- expect(actual["book-v2"]).toEqual(expectedBookV2);
251
+ // Mock resolveFilePath and tryParseUrl to return null or invalid results
252
+ const { resolveFilePath, tryParseUrl } = jest.requireMock("./generate") as {
253
+ resolveFilePath: jest.Mock;
254
+ tryParseUrl: jest.Mock;
255
+ };
256
+ resolveFilePath.mockReturnValue("mock-file-path");
257
+ tryParseUrl.mockReturnValue(null);
258
+
259
+ // Ensure fs.existsSync returns false to force fallback to Kubernetes
260
+ (fs.existsSync as jest.Mock).mockReturnValue(false);
289
261
  });
290
262
 
291
- test("converts CRD to TypeScript and writes to the given directory", async () => {
292
- const options = {
293
- source: "test-crd.yaml",
294
- directory: "test",
263
+ test("should load CRD from Kubernetes cluster", async () => {
264
+ // Mock K8s to return a mocked CRD from the Kubernetes cluster
265
+ const mockCrd = { kind: "CustomResourceDefinition" } as CustomResourceDefinition;
266
+ const mockK8sGet = jest
267
+ .fn<() => Promise<CustomResourceDefinition>>()
268
+ .mockResolvedValue(mockCrd);
269
+ (K8s as jest.Mock).mockReturnValue({ Get: mockK8sGet });
270
+
271
+ // Call the function
272
+ const result = await readOrFetchCrd(mockOpts);
273
+
274
+ // Assert K8s.Get was called with the correct source
275
+ expect(K8s).toHaveBeenCalledWith(CustomResourceDefinition);
276
+ expect(mockK8sGet).toHaveBeenCalledWith("my-crd");
277
+
278
+ // Assert the result matches the mocked CRD
279
+ expect(result).toEqual([mockCrd]);
280
+
281
+ // Assert log function was called with correct message
282
+ expect(mockOpts.logFn).toHaveBeenCalledWith(
283
+ "Attempting to read my-crd from the Kubernetes cluster",
284
+ );
285
+ });
286
+
287
+ test("should log an error if Kubernetes cluster read fails", async () => {
288
+ // Mock K8s to throw an error
289
+ const mockError = new Error("Kubernetes API error");
290
+ const mockK8sGet = jest.fn<() => Promise<never>>().mockRejectedValue(mockError);
291
+ (K8s as jest.Mock).mockReturnValue({ Get: mockK8sGet });
292
+
293
+ // Call the function and assert that it throws an error
294
+ await expect(readOrFetchCrd(mockOpts)).rejects.toThrowError(
295
+ `Failed to read my-crd as a file, URL, or Kubernetes CRD`,
296
+ );
297
+
298
+ // Assert log function was called with error message
299
+ expect(mockOpts.logFn).toHaveBeenCalledWith("Error loading CRD: Kubernetes API error");
300
+
301
+ // Assert K8s.Get was called with the correct source
302
+ expect(K8s).toHaveBeenCalledWith(CustomResourceDefinition);
303
+ expect(mockK8sGet).toHaveBeenCalledWith("my-crd");
304
+ });
305
+ });
306
+
307
+ describe("readOrFetchCrd error handling", () => {
308
+ let mockOpts: GenerateOptions;
309
+
310
+ beforeEach(() => {
311
+ jest.clearAllMocks();
312
+ mockOpts = {
313
+ source: "mock-source",
295
314
  logFn: jest.fn(),
296
315
  };
316
+ });
317
+
318
+ test("should throw an error if file reading fails", async () => {
319
+ (fs.existsSync as jest.Mock).mockReturnValue(true);
320
+ (fs.readFileSync as jest.Mock).mockImplementation(() => {
321
+ throw new Error("File read error");
322
+ });
323
+
324
+ await expect(readOrFetchCrd(mockOpts)).rejects.toThrowError(
325
+ "Failed to read mock-source as a file, URL, or Kubernetes CRD",
326
+ );
297
327
 
298
- await generate(options);
299
- const expectedMovie = [
300
- "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
301
- 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
302
- "/**",
303
- " * Movie nerd",
304
- " */",
305
- "export class Movie extends GenericKind {",
306
- " spec?: Spec;",
307
- "}",
308
- "",
309
- "export interface Spec {",
310
- " author?: string;",
311
- " title?: string;",
312
- "}",
313
- "",
314
- "RegisterKind(Movie, {",
315
- ' group: "example.com",',
316
- ' version: "v1",',
317
- ' kind: "Movie",',
318
- ' plural: "movies",',
319
- "});",
320
- ];
321
- const expectedBookV1 = [
322
- "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
323
- 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
324
- "/**",
325
- " * Book nerd",
326
- " */",
327
- "export class Book extends GenericKind {",
328
- " spec?: Spec;",
329
- "}",
330
- "",
331
- "export interface Spec {",
332
- " author?: string;",
333
- " title?: string;",
334
- "}",
335
- "",
336
- "RegisterKind(Book, {",
337
- ' group: "example.com",',
338
- ' version: "v1",',
339
- ' kind: "Book",',
340
- ' plural: "books",',
341
- "});",
342
- ];
343
- const expectedBookV2 = expectedBookV1
344
- .filter(line => !line.includes("title?"))
345
- .map(line => line.replace("v1", "v2"));
346
-
347
- expect(mkdirSyncSpy).toHaveBeenCalledWith("test", { recursive: true });
348
- expect(writeFileSyncSpy).toHaveBeenCalledWith("test/movie-v1.ts", expectedMovie.join("\n"));
349
- expect(writeFileSyncSpy).toHaveBeenCalledWith("test/book-v1.ts", expectedBookV1.join("\n"));
350
- expect(writeFileSyncSpy).toHaveBeenCalledWith("test/book-v2.ts", expectedBookV2.join("\n"));
328
+ expect(mockOpts.logFn).toHaveBeenCalledWith("Error loading CRD: File read error");
351
329
  });
330
+ });
331
+
332
+ describe("convertCRDtoTS with invalid CRD", () => {
333
+ test("should skip CRD with no versions", async () => {
334
+ const invalidCrd = {
335
+ ...sampleCrd,
336
+ spec: {
337
+ ...sampleCrd.spec,
338
+ versions: [], // CRD with no versions
339
+ },
340
+ };
341
+
342
+ const options = {
343
+ source: "mock-source",
344
+ language: "ts",
345
+ logFn: jest.fn(), // Ensure the mock log function is set
346
+ directory: "test-dir",
347
+ plain: false,
348
+ npmPackage: "kubernetes-fluent-client",
349
+ };
350
+
351
+ const result = await convertCRDtoTS(invalidCrd, options);
352
+
353
+ // Assert that result is empty due to invalid CRD
354
+ expect(result).toEqual([]);
355
+
356
+ // Assert the log function is called with the correct message
357
+ expect(options.logFn).toHaveBeenCalledWith(
358
+ "Skipping movies.example.com, it does not appear to be a CRD",
359
+ );
360
+ });
361
+
362
+ test("should handle schema with no OpenAPI schema", async () => {
363
+ // Modify the sampleCrd to simulate the invalid CRD
364
+ const invalidCrd = {
365
+ ...sampleCrd,
366
+ spec: {
367
+ ...sampleCrd.spec,
368
+ versions: [
369
+ {
370
+ name: "v1",
371
+ served: true,
372
+ storage: true,
373
+ schema: undefined, // No OpenAPI schema
374
+ },
375
+ ],
376
+ },
377
+ };
378
+
379
+ const options = {
380
+ source: "mock-source",
381
+ language: "ts",
382
+ logFn: jest.fn(), // Mock log function
383
+ directory: "test-dir",
384
+ plain: false,
385
+ npmPackage: "kubernetes-fluent-client",
386
+ };
387
+
388
+ // Call the convertCRDtoTS function with the invalid CRD
389
+ const result = await convertCRDtoTS(invalidCrd, options);
390
+
391
+ // Assert that result is empty due to invalid schema
392
+ expect(result).toEqual([]);
352
393
 
353
- test("converts CRD to Go", async () => {
354
- const options = { source: "test-crd.yaml", language: "go", logFn: jest.fn() };
355
-
356
- const actual = await generate(options);
357
- const expectedMovie = [
358
- "// Movie nerd",
359
- "type Movie struct {",
360
- '\tSpec *Spec `json:"spec,omitempty"`',
361
- "}",
362
- "",
363
- "type Spec struct {",
364
- '\tAuthor *string `json:"author,omitempty"`',
365
- '\tTitle *string `json:"title,omitempty"`',
366
- "}",
367
- "",
368
- ];
369
- const expectedBookV1 = [
370
- "// Book nerd",
371
- "type Book struct {",
372
- '\tSpec *Spec `json:"spec,omitempty"`',
373
- "}",
374
- "",
375
- "type Spec struct {",
376
- '\tAuthor *string `json:"author,omitempty"`',
377
- '\tTitle *string `json:"title,omitempty"`',
378
- "}",
379
- "",
380
- ];
381
- const expectedBookV2 = expectedBookV1
382
- .filter(line => !line.includes("Title"))
383
- .map(line => line.replace("v1", "v2"));
384
-
385
- expect(actual["movie-v1"]).toEqual(expectedMovie);
386
- expect(actual["book-v1"]).toEqual(expectedBookV1);
387
- expect(actual["book-v2"]).toEqual(expectedBookV2);
394
+ // Assert that the log function was called with the appropriate message
395
+ expect(options.logFn).toHaveBeenCalledWith(
396
+ "Skipping movies.example.com, it does not appear to have a valid schema",
397
+ );
388
398
  });
389
399
  });