From 43b3453e24ce79469f81a5f55bc01ff02888c3e6 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jun 2020 15:21:28 +0300 Subject: [PATCH] fixed all src/** tests, moved from spec/src to source code locations --- __mocks__/electron.js | 3 +- .../helpers/utils.ts | 0 .../specs/app_spec.ts | 0 package.json | 6 +- .../src => src}/common/cluster-store_spec.ts | 14 +-- {spec/src => src}/common/user-store_spec.ts | 12 +- .../arrays.test.ts => splitArray_test.ts} | 2 +- src/common/vars.ts | 1 + {spec/src => src}/main/kubectl_spec.ts | 12 +- {spec/src => src}/main/port_spec.ts | 23 ++-- src/migrations/cluster-store/2.0.0-beta.2.ts | 6 +- src/migrations/cluster-store/2.4.1.ts | 8 +- src/migrations/cluster-store/2.6.0-beta.2.ts | 6 +- src/migrations/cluster-store/2.6.0-beta.3.ts | 5 +- src/migrations/cluster-store/2.7.0-beta.0.ts | 6 +- src/migrations/cluster-store/2.7.0-beta.1.ts | 5 +- src/renderer/api/kube-api-parse.ts | 103 +++++++++++++++++ ...arseAPI.test.ts => kube-api-parse_test.ts} | 11 +- src/renderer/api/kube-api.ts | 104 +----------------- src/renderer/utils/__tests__/arrays.test.ts | 0 20 files changed, 167 insertions(+), 160 deletions(-) rename {spec/integration => integration}/helpers/utils.ts (100%) rename {spec/integration => integration}/specs/app_spec.ts (100%) rename {spec/src => src}/common/cluster-store_spec.ts (97%) rename {spec/src => src}/common/user-store_spec.ts (90%) rename src/common/utils/{__tests__/arrays.test.ts => splitArray_test.ts} (95%) rename {spec/src => src}/main/kubectl_spec.ts (62%) rename {spec/src => src}/main/port_spec.ts (51%) create mode 100644 src/renderer/api/kube-api-parse.ts rename src/renderer/api/{__test__/parseAPI.test.ts => kube-api-parse_test.ts} (90%) delete mode 100644 src/renderer/utils/__tests__/arrays.test.ts diff --git a/__mocks__/electron.js b/__mocks__/electron.js index f07751b346..8e744a11a8 100644 --- a/__mocks__/electron.js +++ b/__mocks__/electron.js @@ -3,7 +3,8 @@ module.exports = { match: jest.fn(), app: { getVersion: jest.fn().mockReturnValue("3.0.0"), - getPath: jest.fn().mockReturnValue("/foo/bar") + getPath: jest.fn().mockReturnValue("tmp"), + getLocale: jest.fn().mockRejectedValue("en"), }, remote: { app: { diff --git a/spec/integration/helpers/utils.ts b/integration/helpers/utils.ts similarity index 100% rename from spec/integration/helpers/utils.ts rename to integration/helpers/utils.ts diff --git a/spec/integration/specs/app_spec.ts b/integration/specs/app_spec.ts similarity index 100% rename from spec/integration/specs/app_spec.ts rename to integration/specs/app_spec.ts diff --git a/package.json b/package.json index cd51dcc8af..e2cdeedd92 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "build:linux": "yarn compile && electron-builder --linux --dir -c.productName=LensDev", "build:mac": "yarn compile && electron-builder --mac --dir -c.productName=LensDev", "build:win": "yarn compile && electron-builder --win --dir -c.productName=LensDev", - "test": "jest spec/src $@", - "integration": "jest spec/integration $@", + "test": "jest --env=jsdom src $@", + "integration": "jest --coverage integration $@", "dist": "yarn compile && electron-builder -p onTag", "dist:win": "yarn compile && electron-builder -p onTag --x64 --ia32", "dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null", @@ -63,7 +63,7 @@ ] }, "jest": { - "testRegex": ".*_(spec)\\.[jt]sx?$", + "testRegex": ".*_(spec|test)\\.[jt]sx?$", "collectCoverage": false, "verbose": true, "testEnvironment": "node", diff --git a/spec/src/common/cluster-store_spec.ts b/src/common/cluster-store_spec.ts similarity index 97% rename from spec/src/common/cluster-store_spec.ts rename to src/common/cluster-store_spec.ts index f2e5a69c5b..6062152d97 100644 --- a/spec/src/common/cluster-store_spec.ts +++ b/src/common/cluster-store_spec.ts @@ -1,17 +1,7 @@ import mockFs from "mock-fs" import yaml from "js-yaml" -import { ClusterStore } from "../../../src/common/cluster-store"; -import { Cluster } from "../../../src/main/cluster"; - -jest.mock("electron", () => { - return { - app: { - getVersion: () => '99.99.99', - getPath: () => 'tmp', - getLocale: () => 'en' - } - } -}) +import { ClusterStore } from "./cluster-store"; +import { Cluster } from "../main/cluster"; // Console.log needs to be called before fs-mocks, see https://github.com/tschaub/mock-fs/issues/234 console.log(""); diff --git a/spec/src/common/user-store_spec.ts b/src/common/user-store_spec.ts similarity index 90% rename from spec/src/common/user-store_spec.ts rename to src/common/user-store_spec.ts index d6c06ad91c..7a4a42fe21 100644 --- a/spec/src/common/user-store_spec.ts +++ b/src/common/user-store_spec.ts @@ -1,15 +1,7 @@ import mockFs from "mock-fs" -import { userStore, UserStore } from "../../../src/common/user-store" +import { userStore, UserStore } from "./user-store" -jest.mock("electron", () => { - return { - app: { - getVersion: () => '99.99.99', - getPath: () => 'tmp', - getLocale: () => 'en' - } - } -}) +jest.mock("electron") // Console.log needs to be called before fs-mocks, see https://github.com/tschaub/mock-fs/issues/234 console.log(""); diff --git a/src/common/utils/__tests__/arrays.test.ts b/src/common/utils/splitArray_test.ts similarity index 95% rename from src/common/utils/__tests__/arrays.test.ts rename to src/common/utils/splitArray_test.ts index 8670db4d04..ede542d605 100644 --- a/src/common/utils/__tests__/arrays.test.ts +++ b/src/common/utils/splitArray_test.ts @@ -1,4 +1,4 @@ -import { splitArray } from "../splitArray"; +import { splitArray } from "./splitArray"; describe("split array on element tests", () => { test("empty array", () => { diff --git a/src/common/vars.ts b/src/common/vars.ts index 353fed396c..e142ce6193 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -12,6 +12,7 @@ export const isDebugging = process.env.DEBUG === "true"; export const isProduction = process.env.NODE_ENV === "production" export const isDevelopment = isDebugging || !isProduction; export const buildVersion = process.env.BUILD_VERSION; +export const isTestEnv = !!process.env.JEST_WORKER_ID; // Paths export const contextDir = process.cwd(); diff --git a/spec/src/main/kubectl_spec.ts b/src/main/kubectl_spec.ts similarity index 62% rename from spec/src/main/kubectl_spec.ts rename to src/main/kubectl_spec.ts index 0754f0410a..4d6e89df23 100644 --- a/spec/src/main/kubectl_spec.ts +++ b/src/main/kubectl_spec.ts @@ -1,14 +1,10 @@ -import packageInfo from "../../../package.json" - -// fixme: ENOENT: no such file or directory, mkdir '/foo/bar' -// import { bundledKubectl, Kubectl } from "../../../src/main/kubectl"; -var bundledKubectl: any; -var Kubectl: any; +import packageInfo from "../../package.json" +import { bundledKubectl, Kubectl } from "../../src/main/kubectl"; jest.mock("electron") -jest.mock("../../../src/common/user-store") +jest.mock("../common/user-store") -xdescribe("kubectlVersion", () => { +describe("kubectlVersion", () => { it("returns bundled version if exactly same version used", async () => { const kubectl = new Kubectl(bundledKubectl.kubectlVersion) expect(kubectl.kubectlVersion).toBe(bundledKubectl.kubectlVersion) diff --git a/spec/src/main/port_spec.ts b/src/main/port_spec.ts similarity index 51% rename from spec/src/main/port_spec.ts rename to src/main/port_spec.ts index 8c7566e02c..0600d07fb9 100644 --- a/spec/src/main/port_spec.ts +++ b/src/main/port_spec.ts @@ -1,24 +1,25 @@ import { EventEmitter } from 'events' +import { getFreePort } from "./port" +import net from "net" + +jest.mock("net"); class MockServer extends EventEmitter { listen = jest.fn((obj) => { this.emit('listening', {}) return this }) - address = () => { return { port: 12345 }} + address = () => { + return { port: 12345 } + } unref = jest.fn() - close = jest.fn((cb) => { - cb() - }) + close = jest.fn(cb => cb()) } -// eslint-disable-next-line @typescript-eslint/no-var-requires -const net = require("net") -jest.mock("net") -import * as port from "../../../src/main/port" - -describe("getFreePort", () => { +describe.only("getFreePort", () => { beforeEach(() => { + // @ts-ignore + // fixme: find a better way to support types for mocked module net.createServer.mockReturnValue(new MockServer) }) @@ -27,6 +28,6 @@ describe("getFreePort", () => { }) it("finds the next free port", async () => { - return expect(port.getFreePort()).resolves.toEqual(expect.any(Number)) + return expect(getFreePort()).resolves.toEqual(expect.any(Number)) }) }) diff --git a/src/migrations/cluster-store/2.0.0-beta.2.ts b/src/migrations/cluster-store/2.0.0-beta.2.ts index 7d2b4ac8de..3ec4f948f5 100644 --- a/src/migrations/cluster-store/2.0.0-beta.2.ts +++ b/src/migrations/cluster-store/2.0.0-beta.2.ts @@ -1,8 +1,12 @@ /* Early store format had the kubeconfig directly under context name, this moves it under the kubeConfig key */ +import { isTestEnv } from "../../common/vars"; + export function migration(store: any) { - console.log("CLUSTER STORE, MIGRATION: 2.0.0-beta.2"); + if(!isTestEnv) { + console.log("CLUSTER STORE, MIGRATION: 2.0.0-beta.2"); + } for (const value of store) { const contextName = value[0]; // Looping all the keys gives out the store internal stuff too... diff --git a/src/migrations/cluster-store/2.4.1.ts b/src/migrations/cluster-store/2.4.1.ts index 94dd8df7cd..7652ebedf6 100644 --- a/src/migrations/cluster-store/2.4.1.ts +++ b/src/migrations/cluster-store/2.4.1.ts @@ -1,9 +1,13 @@ // Cleans up a store that had the state related data stored +import { isTestEnv } from "../../common/vars"; + export function migration(store: any) { - console.log("CLUSTER STORE, MIGRATION: 2.4.1"); + if (!isTestEnv) { + console.log("CLUSTER STORE, MIGRATION: 2.4.1"); + } for (const value of store) { const contextName = value[0]; - if(contextName === "__internal__") continue; + if (contextName === "__internal__") continue; const cluster = value[1]; store.set(contextName, { kubeConfig: cluster.kubeConfig, icon: cluster.icon || null, preferences: cluster.preferences || {} }); diff --git a/src/migrations/cluster-store/2.6.0-beta.2.ts b/src/migrations/cluster-store/2.6.0-beta.2.ts index 118aa8aaf9..48d5b48734 100644 --- a/src/migrations/cluster-store/2.6.0-beta.2.ts +++ b/src/migrations/cluster-store/2.6.0-beta.2.ts @@ -1,6 +1,10 @@ // Move cluster icon from root to preferences +import { isTestEnv } from "../../common/vars"; + export function migration(store: any) { - console.log("CLUSTER STORE, MIGRATION: 2.6.0-beta.2"); + if(!isTestEnv) { + console.log("CLUSTER STORE, MIGRATION: 2.6.0-beta.2"); + } for (const value of store) { const clusterKey = value[0]; if(clusterKey === "__internal__") continue diff --git a/src/migrations/cluster-store/2.6.0-beta.3.ts b/src/migrations/cluster-store/2.6.0-beta.3.ts index fa6eb4e7e3..d8063764c0 100644 --- a/src/migrations/cluster-store/2.6.0-beta.3.ts +++ b/src/migrations/cluster-store/2.6.0-beta.3.ts @@ -1,8 +1,11 @@ import * as yaml from "js-yaml" +import { isTestEnv } from "../../common/vars"; // Convert access token and expiry from arrays into strings export function migration(store: any) { - console.log("CLUSTER STORE, MIGRATION: 2.6.0-beta.3"); + if(!isTestEnv) { + console.log("CLUSTER STORE, MIGRATION: 2.6.0-beta.3"); + } for (const value of store) { const clusterKey = value[0]; if(clusterKey === "__internal__") continue diff --git a/src/migrations/cluster-store/2.7.0-beta.0.ts b/src/migrations/cluster-store/2.7.0-beta.0.ts index 049fef8a21..1b567fe933 100644 --- a/src/migrations/cluster-store/2.7.0-beta.0.ts +++ b/src/migrations/cluster-store/2.7.0-beta.0.ts @@ -1,6 +1,10 @@ // Add existing clusters to "default" workspace +import { isTestEnv } from "../../common/vars"; + export function migration(store: any) { - console.log("CLUSTER STORE, MIGRATION: 2.7.0-beta.0"); + if(!isTestEnv) { + console.log("CLUSTER STORE, MIGRATION: 2.7.0-beta.0"); + } for (const value of store) { const clusterKey = value[0]; if(clusterKey === "__internal__") continue diff --git a/src/migrations/cluster-store/2.7.0-beta.1.ts b/src/migrations/cluster-store/2.7.0-beta.1.ts index 3fd42a4c99..4918041245 100644 --- a/src/migrations/cluster-store/2.7.0-beta.1.ts +++ b/src/migrations/cluster-store/2.7.0-beta.1.ts @@ -1,8 +1,11 @@ // add id for clusters and store them to array import { v4 as uuid } from "uuid" +import { isTestEnv } from "../../common/vars"; export function migration(store: any) { - console.log("CLUSTER STORE, MIGRATION: 2.7.0-beta.1"); + if(!isTestEnv) { + console.log("CLUSTER STORE, MIGRATION: 2.7.0-beta.1"); + } const clusters: any[] = [] for (const value of store) { const clusterKey = value[0]; diff --git a/src/renderer/api/kube-api-parse.ts b/src/renderer/api/kube-api-parse.ts new file mode 100644 index 0000000000..0745dc71eb --- /dev/null +++ b/src/renderer/api/kube-api-parse.ts @@ -0,0 +1,103 @@ +// Parse kube-api path and get api-version, group, etc. +import { splitArray } from "../../common/utils"; + +export interface IKubeApiLinkRef { + apiPrefix?: string; + apiVersion: string; + resource: string; + name: string; + namespace?: string; +} + +export interface IKubeApiLinkBase extends IKubeApiLinkRef { + apiBase: string; + apiGroup: string; + apiVersionWithGroup: string; +} + +export function parseApi(path: string): IKubeApiLinkBase { + path = new URL(path, location.origin).pathname; + const [, prefix, ...parts] = path.split("/"); + const apiPrefix = `/${prefix}`; + + const [left, right, namespaced] = splitArray(parts, "namespaces"); + let apiGroup, apiVersion, namespace, resource, name; + + if (namespaced) { + switch (right.length) { + case 1: + name = right[0]; + // fallthrough + case 0: + resource = "namespaces"; // special case this due to `split` removing namespaces + break; + default: + [namespace, resource, name] = right; + break; + } + + apiVersion = left.pop(); + apiGroup = left.join("/"); + } else { + switch (left.length) { + case 2: + resource = left.pop(); + // fallthrough + case 1: + apiVersion = left.pop(); + apiGroup = ""; + break; + default: + /** + * Given that + * - `apiVersion` is `GROUP/VERSION` and + * - `VERSION` is `DNS_LABEL` which is /^[a-z0-9]((-[a-z0-9])|[a-z0-9])*$/i + * where length <= 63 + * - `GROUP` is /^D(\.D)*$/ where D is `DNS_LABEL` and length <= 253 + * + * There is no well defined selection from an array of items that were + * seperated by '/' + * + * Solution is to create a huristic. Namely: + * 1. if '.' in left[0] then apiGroup <- left[0] + * 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1] + * 3. otherwise assume apiVersion <- left[0] + * 4. always resource, name <- left[(0 or 1)+1..] + */ + if (left[0].includes('.') || left[1].match(/^v[0-9]/)) { + [apiGroup, apiVersion] = left; + resource = left.slice(2).join("/") + } else { + apiGroup = ""; + apiVersion = left[0]; + [resource, name] = left.slice(1) + } + break; + } + } + + const apiVersionWithGroup = [apiGroup, apiVersion].filter(v => v).join("/"); + const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/"); + + if (!apiBase) { + throw new Error(`invalid apiPath: ${path}`) + } + + return { + apiBase, + apiPrefix, apiGroup, + apiVersion, apiVersionWithGroup, + namespace, resource, name, + }; +} + +export function createApiLink(ref: IKubeApiLinkRef): string { + const { apiPrefix = "/apis", resource, apiVersion, name } = ref; + let { namespace } = ref; + if (namespace) { + namespace = `namespaces/${namespace}` + } + return [apiPrefix, apiVersion, namespace, resource, name] + .filter(v => v) + .join("/") +} diff --git a/src/renderer/api/__test__/parseAPI.test.ts b/src/renderer/api/kube-api-parse_test.ts similarity index 90% rename from src/renderer/api/__test__/parseAPI.test.ts rename to src/renderer/api/kube-api-parse_test.ts index fa7e21f0b1..03f53ae34d 100644 --- a/src/renderer/api/__test__/parseAPI.test.ts +++ b/src/renderer/api/kube-api-parse_test.ts @@ -1,11 +1,11 @@ -import { IKubeApiLinkBase, KubeApi } from "../kube-api"; +import { IKubeApiLinkBase, parseApi } from "./kube-api-parse"; -interface ParseAPITest { +interface KubeApi_Parse_Test { url: string; expected: Required; } -const tests: ParseAPITest[] = [ +const tests: KubeApi_Parse_Test[] = [ { url: "/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27", expected: { @@ -112,12 +112,11 @@ const tests: ParseAPITest[] = [ }, ]; -jest.mock('../kube-watch-api.ts', () => 'KubeWatchApi'); -describe("parseApi unit tests", () => { +describe.only("parseApi unit tests", () => { for (const i in tests) { const { url: tUrl, expected:tExpect} = tests[i]; test(`test #${parseInt(i)+1}`, () => { - expect(KubeApi.parseApi(tUrl)).toStrictEqual(tExpect); + expect(parseApi(tUrl)).toStrictEqual(tExpect); }); } }); diff --git a/src/renderer/api/kube-api.ts b/src/renderer/api/kube-api.ts index 509a07fcba..869e3a90ea 100644 --- a/src/renderer/api/kube-api.ts +++ b/src/renderer/api/kube-api.ts @@ -7,7 +7,7 @@ import { IKubeObjectRef, KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } fro import { apiKube } from "./index"; import { kubeWatchApi } from "./kube-watch-api"; import { apiManager } from "./api-manager"; -import { splitArray } from "../../common/utils"; +import { createApiLink, parseApi } from "./kube-api-parse"; export interface IKubeApiOptions { kind: string; // resource type within api-group, e.g. "Namespace" @@ -25,107 +25,9 @@ export interface IKubeApiQueryParams { continue?: string; // might be used with ?limit from second request } -export interface IKubeApiLinkRef { - apiPrefix?: string; - apiVersion: string; - resource: string; - name: string; - namespace?: string; -} - -export interface IKubeApiLinkBase extends IKubeApiLinkRef { - apiBase: string; - apiGroup: string; - apiVersionWithGroup: string; -} - export class KubeApi { - static parseApi(apiPath = ""): IKubeApiLinkBase { - apiPath = new URL(apiPath, location.origin).pathname; - const [, prefix, ...parts] = apiPath.split("/"); - const apiPrefix = `/${prefix}`; - - const [left, right, namespaced] = splitArray(parts, "namespaces"); - let apiGroup, apiVersion, namespace, resource, name; - - if (namespaced) { - switch (right.length) { - case 1: - name = right[0]; - // fallthrough - case 0: - resource = "namespaces"; // special case this due to `split` removing namespaces - break; - default: - [namespace, resource, name] = right; - break; - } - - apiVersion = left.pop(); - apiGroup = left.join("/"); - } else { - switch (left.length) { - case 2: - resource = left.pop(); - // fallthrough - case 1: - apiVersion = left.pop(); - apiGroup = ""; - break; - default: - /** - * Given that - * - `apiVersion` is `GROUP/VERSION` and - * - `VERSION` is `DNS_LABEL` which is /^[a-z0-9]((-[a-z0-9])|[a-z0-9])*$/i - * where length <= 63 - * - `GROUP` is /^D(\.D)*$/ where D is `DNS_LABEL` and length <= 253 - * - * There is no well defined selection from an array of items that were - * seperated by '/' - * - * Solution is to create a huristic. Namely: - * 1. if '.' in left[0] then apiGroup <- left[0] - * 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1] - * 3. otherwise assume apiVersion <- left[0] - * 4. always resource, name <- left[(0 or 1)+1..] - */ - if (left[0].includes('.') || left[1].match(/^v[0-9]/)) { - [apiGroup, apiVersion] = left; - resource = left.slice(2).join("/") - } else { - apiGroup = ""; - apiVersion = left[0]; - [resource, name] = left.slice(1) - } - break; - } - } - - const apiVersionWithGroup = [apiGroup, apiVersion].filter(v => v).join("/"); - const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/"); - - if (!apiBase) { - throw new Error(`invalid apiPath: ${apiPath}`) - } - - return { - apiBase, - apiPrefix, apiGroup, - apiVersion, apiVersionWithGroup, - namespace, resource, name, - }; - } - - static createLink(ref: IKubeApiLinkRef): string { - const { apiPrefix = "/apis", resource, apiVersion, name } = ref; - let { namespace } = ref; - if (namespace) { - namespace = `namespaces/${namespace}` - } - return [apiPrefix, apiVersion, namespace, resource, name] - .filter(v => v) - .join("/") - } + static parseApi = parseApi; + static createLink = createApiLink; static watchAll(...apis: KubeApi[]) { const disposers = apis.map(api => api.watch()); diff --git a/src/renderer/utils/__tests__/arrays.test.ts b/src/renderer/utils/__tests__/arrays.test.ts deleted file mode 100644 index e69de29bb2..0000000000