1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Better fix for formatting urls

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-08-17 12:45:01 -04:00
parent 26ea3f4a80
commit 3ef643190c
2 changed files with 172 additions and 11 deletions

View File

@ -80,7 +80,7 @@ describe("createKubeApiForRemoteCluster", () => {
it("should request pods from default namespace", () => {
expect(fetchMock.mock.lastCall).toMatchObject([
"https://127.0.0.1:6443/api/v1/namespaces/default/pods",
"https://127.0.0.1:6443/api/v1/pods",
{
headers: {
"content-type": "application/json",
@ -93,7 +93,7 @@ describe("createKubeApiForRemoteCluster", () => {
describe("when request resolves with data", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["https://127.0.0.1:6443/api/v1/namespaces/default/pods"],
["https://127.0.0.1:6443/api/v1/pods"],
new Response(JSON.stringify({
kind: "PodList",
apiVersion: "v1",
@ -1495,4 +1495,138 @@ describe("KubeApi", () => {
});
});
});
describe("listing pods", () => {
let api: PodApi;
beforeEach(() => {
api = new PodApi({
request,
});
});
describe("when listing pods with no descriptor", () => {
let listRequest: Promise<Pod[] | null>;
beforeEach(async () => {
listRequest = api.list();
await flushPromises();
});
it("should request that the pods from all namespaces", () => {
expect(fetchMock.mock.lastCall).toMatchObject([
"http://127.0.0.1:9999/api-kube/api/v1/pods",
{
headers: {
"content-type": "application/json",
},
method: "get",
},
]);
});
describe("when the request resolves with empty data", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/pods"],
new Response(JSON.stringify({
kind: "PodList",
apiVersion: "v1",
metadata: {},
items: [],
})),
);
});
it("the call should resolve to an empty list", async () => {
expect(await listRequest).toEqual([]);
});
});
});
describe("when listing pods with descriptor with namespace=''", () => {
let listRequest: Promise<Pod[] | null>;
beforeEach(async () => {
listRequest = api.list({
namespace: "",
});
await flushPromises();
});
it("should request that the pods from all namespaces", () => {
expect(fetchMock.mock.lastCall).toMatchObject([
"http://127.0.0.1:9999/api-kube/api/v1/pods",
{
headers: {
"content-type": "application/json",
},
method: "get",
},
]);
});
describe("when the request resolves with empty data", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/pods"],
new Response(JSON.stringify({
kind: "PodList",
apiVersion: "v1",
metadata: {},
items: [],
})),
);
});
it("the call should resolve to an empty list", async () => {
expect(await listRequest).toEqual([]);
});
});
});
describe("when listing pods with descriptor with namespace='default'", () => {
let listRequest: Promise<Pod[] | null>;
beforeEach(async () => {
listRequest = api.list({
namespace: "default",
});
await flushPromises();
});
it("should request that the pods from just the default namespace", () => {
expect(fetchMock.mock.lastCall).toMatchObject([
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods",
{
headers: {
"content-type": "application/json",
},
method: "get",
},
]);
});
describe("when the request resolves with empty data", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods"],
new Response(JSON.stringify({
kind: "PodList",
apiVersion: "v1",
metadata: {},
items: [],
})),
);
});
it("the call should resolve to an empty list", async () => {
expect(await listRequest).toEqual([]);
});
});
});
});
});

View File

@ -393,13 +393,33 @@ export class KubeApi<
});
}
getUrl({ name, namespace }: Partial<ResourceDescriptor> = {}, query?: Partial<KubeApiQueryParams>) {
/**
* This method differs from {@link formatUrlForNotListing} because this treats `""` as "all namespaces"
* @param namespace The namespace to list in or `""` for all namespaces
*/
formatUrlForListing(namespace: string) {
return createKubeApiURL({
apiPrefix: this.apiPrefix,
apiVersion: this.apiVersionWithGroup,
resource: this.apiResource,
namespace: this.isNamespaced
? namespace ?? "default"
: undefined,
});
}
/**
* Format a URL pathname and query for acting upon a specific resource.
*/
formatUrlForNotListing(resource?: Partial<ResourceDescriptor>, query?: Partial<KubeApiQueryParams>): string;
formatUrlForNotListing({ name, namespace }: Partial<ResourceDescriptor> = {}, query?: Partial<KubeApiQueryParams>) {
const resourcePath = createKubeApiURL({
apiPrefix: this.apiPrefix,
apiVersion: this.apiVersionWithGroup,
resource: this.apiResource,
namespace: this.isNamespaced
? namespace ?? "default" // allow `""` to mean all namespaces
? namespace || "default"
: undefined,
name,
});
@ -407,6 +427,13 @@ export class KubeApi<
return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : "");
}
/**
* @deprecated use {@link formatUrlForNotListing} instead
*/
getUrl(resource?: Partial<ResourceDescriptor>, query?: Partial<KubeApiQueryParams>) {
return this.formatUrlForNotListing(resource, query);
}
protected normalizeQuery(query: Partial<KubeApiQueryParams> = {}) {
if (query.labelSelector) {
query.labelSelector = [query.labelSelector].flat().join(",");
@ -484,7 +511,7 @@ export class KubeApi<
async list({ namespace = "", reqInit }: KubeApiListOptions = {}, query?: KubeApiQueryParams): Promise<Object[] | null> {
await this.checkPreferredVersion();
const url = this.getUrl({ namespace });
const url = this.formatUrlForListing(namespace);
const res = await this.request.get(url, { query }, reqInit);
const parsed = this.parseResponse(res, namespace);
@ -502,7 +529,7 @@ export class KubeApi<
async get(desc: ResourceDescriptor, query?: KubeApiQueryParams): Promise<Object | null> {
await this.checkPreferredVersion();
const url = this.getUrl(desc);
const url = this.formatUrlForNotListing(desc);
const res = await this.request.get(url, { query });
const parsed = this.parseResponse(res);
@ -516,7 +543,7 @@ export class KubeApi<
async create({ name, namespace }: Partial<ResourceDescriptor>, partialData?: PartialDeep<Object>): Promise<Object | null> {
await this.checkPreferredVersion();
const apiUrl = this.getUrl({ namespace });
const apiUrl = this.formatUrlForNotListing({ namespace });
const data = merge(partialData, {
kind: this.kind,
apiVersion: this.apiVersionWithGroup,
@ -537,7 +564,7 @@ export class KubeApi<
async update({ name, namespace }: ResourceDescriptor, data: PartialDeep<Object>): Promise<Object | null> {
await this.checkPreferredVersion();
const apiUrl = this.getUrl({ namespace, name });
const apiUrl = this.formatUrlForNotListing({ namespace, name });
const res = await this.request.put(apiUrl, {
data: merge(data, {
@ -562,7 +589,7 @@ export class KubeApi<
async patch(desc: ResourceDescriptor, data: PartialDeep<Object> | Patch, strategy: KubeApiPatchType): Promise<Object | null>;
async patch(desc: ResourceDescriptor, data: PartialDeep<Object> | Patch, strategy: KubeApiPatchType = "strategic"): Promise<Object | null> {
await this.checkPreferredVersion();
const apiUrl = this.getUrl(desc);
const apiUrl = this.formatUrlForNotListing(desc);
const res = await this.request.patch(apiUrl, { data }, {
headers: {
@ -580,7 +607,7 @@ export class KubeApi<
async delete({ propagationPolicy = "Background", ...desc }: DeleteResourceDescriptor) {
await this.checkPreferredVersion();
const apiUrl = this.getUrl(desc);
const apiUrl = this.formatUrlForNotListing(desc);
return this.request.del(apiUrl, {
query: {
@ -590,7 +617,7 @@ export class KubeApi<
}
getWatchUrl(namespace?: string, query: KubeApiQueryParams = {}) {
return this.getUrl({ namespace }, {
return this.formatUrlForNotListing({ namespace }, {
watch: 1,
resourceVersion: this.getResourceVersion(namespace),
...query,