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.
- package/.prettierignore +4 -0
- package/README.md +24 -0
- package/dist/cli.js +21 -1
- package/dist/fileSystem.d.ts +11 -0
- package/dist/fileSystem.d.ts.map +1 -0
- package/dist/fileSystem.js +42 -0
- package/dist/fileSystem.test.d.ts +2 -0
- package/dist/fileSystem.test.d.ts.map +1 -0
- package/dist/fileSystem.test.js +75 -0
- package/dist/fluent/watch.d.ts +2 -0
- package/dist/fluent/watch.d.ts.map +1 -1
- package/dist/fluent/watch.js +147 -27
- package/dist/generate.d.ts +71 -11
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +130 -117
- package/dist/generate.test.js +293 -346
- package/dist/postProcessing.d.ts +246 -0
- package/dist/postProcessing.d.ts.map +1 -0
- package/dist/postProcessing.js +497 -0
- package/dist/postProcessing.test.d.ts +2 -0
- package/dist/postProcessing.test.d.ts.map +1 -0
- package/dist/postProcessing.test.js +550 -0
- package/e2e/cli.e2e.test.ts +127 -0
- package/e2e/crds/policyreports.default.expected/policyreport-v1alpha1.ts +332 -0
- package/e2e/crds/policyreports.default.expected/policyreport-v1alpha2.ts +360 -0
- package/e2e/crds/policyreports.default.expected/policyreport-v1beta1.ts +360 -0
- package/e2e/crds/policyreports.no.post.expected/policyreport-v1alpha1.ts +331 -0
- package/e2e/crds/policyreports.no.post.expected/policyreport-v1alpha2.ts +360 -0
- package/e2e/crds/policyreports.no.post.expected/policyreport-v1beta1.ts +360 -0
- package/e2e/crds/test.yaml/policyreports.test.yaml +1008 -0
- package/e2e/crds/test.yaml/uds-podmonitors.test.yaml +1245 -0
- package/e2e/crds/uds-podmonitors.default.expected/podmonitor-v1.ts +1333 -0
- package/e2e/crds/uds-podmonitors.no.post.expected/podmonitor-v1.ts +1360 -0
- package/package.json +6 -5
- package/src/cli.ts +25 -1
- package/src/fileSystem.test.ts +67 -0
- package/src/fileSystem.ts +25 -0
- package/src/fluent/watch.ts +174 -35
- package/src/generate.test.ts +368 -358
- package/src/generate.ts +173 -154
- package/src/postProcessing.test.ts +742 -0
- package/src/postProcessing.ts +568 -0
package/src/generate.test.ts
CHANGED
|
@@ -1,389 +1,399 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
2
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return
|
|
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
|
-
|
|
126
|
-
|
|
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("
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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("
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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("
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
});
|