Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,44 @@
describe("Servers table page", () => {
beforeEach(() => {
cy.login();
cy.visit("/core/servers");
});
it("Filters servers by hostname", () => {
cy.visit("/core/servers");
cy.get("input[name=fuzzControl]").focus().type("edge");
cy.window().its("location.search").should("contain", "search=edge");
});
it("Queues and clears revalidations on a server", () => {
cy.get("input[name=fuzzControl]").focus().type("edge");

// We need to force re-rendering of the table every time we do
// something, or cypress moves too fast and undoes things it's doing
// before the effects can be seen. This could be fixed by splitting
// these into separate tests, but that wouldn't be faster and would have
// the added drawback that it depends on the initial state of the data
// and the order in which the tests are run.
const reload = (): void => {
cy.reload();
cy.get("button[aria-label='column visibility menu']").click();
cy.get("input[type=checkbox][name='Reval Pending']").check();
cy.get("body").click(); // closes the menu so you can interact with other things.
};

reload();

cy.get(".ag-row:visible").first().rightclick();
cy.get("button").contains("Queue Content Revalidation").click();
reload();

cy.get(".ag-cell[col-id=revalPending]").first().should("contain.text", "schedule");
cy.get(".ag-row:visible").first().rightclick();
cy.get("button").contains("Clear Queued Content Revalidations").click();
reload();

cy.get(".ag-cell[col-id=revalPending]").first().should("contain.text", "done");
cy.get(".ag-row:visible").first().rightclick();
cy.get("button").contains("Queue Content Revalidation").click();
reload();

cy.get(".ag-cell[col-id=revalPending]").first().should("contain.text", "schedule");
});
});
121 changes: 118 additions & 3 deletions experimental/traffic-portal/src/app/api/server.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { TestBed } from "@angular/core/testing";
import { type ResponseServer } from "trafficops-types";
import { ResponseServerCapability, type ResponseServer } from "trafficops-types";

import { ServerService } from "./server.service";

Expand Down Expand Up @@ -154,7 +154,15 @@ describe("ServerService", () => {
await expectAsync(resp).toBeResolvedTo(server);
});

it("delete a server", async () => {
it("throws an error for invalid call signatures to updateServer", async () => {
const responseP = (service as unknown as {updateServer: (id: number) => Promise<ResponseServer>}).updateServer(
server.id
);
httpTestingController.expectNone({method: "PUT"});
await expectAsync(responseP).toBeRejected();
});

it("deletes a server", async () => {
const resp = service.deleteServer(server);
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/servers/${server.id}`);
expect(req.request.method).toBe("DELETE");
Expand All @@ -164,7 +172,7 @@ describe("ServerService", () => {
await expectAsync(resp).toBeResolvedTo(server);
});

it("delete a server by ID", async () => {
it("deletes a server by ID", async () => {
const resp = service.deleteServer(server.id);
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/servers/${server.id}`);
expect(req.request.method).toBe("DELETE");
Expand Down Expand Up @@ -264,6 +272,77 @@ describe("ServerService", () => {
});
});

describe("Capability-related methods", () => {
const capability: ResponseServerCapability = {
lastUpdated: new Date(),
name: "testquest",
};

it("sends requests for multiple capabilities", async () => {
const responseP = service.getCapabilities();
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("GET");
req.flush({response: [capability]});
await expectAsync(responseP).toBeResolvedTo([capability]);
});
it("sends requests for a single capability by name", async () => {
const responseP = service.getCapabilities(capability.name);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.method).toBe("GET");
req.flush({response: [capability]});
await expectAsync(responseP).toBeResolvedTo(capability);
});
it("throws an error when fetching a non-existent capability", async () => {
const responseP = service.getCapabilities(capability.name);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.method).toBe("GET");
req.flush({response: []});
await expectAsync(responseP).toBeRejected();
});
it("creates a new capability", async () => {
const responseP = service.createCapability(capability);
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("POST");
expect(req.request.body).toEqual(capability);
req.flush({response: capability});
await expectAsync(responseP).toBeResolvedTo(capability);
});
it("updates an existing capability", async () => {
const responseP = service.updateCapability(capability.name, capability);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("PUT");
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.body).toEqual(capability);
req.flush({response: capability});
await expectAsync(responseP).toBeResolvedTo(capability);
});
it("deletes server_capabilities", async () => {
const responseP = service.deleteCapability(capability);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("DELETE");
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("deletes server_capabilities by name", async () => {
const responseP = service.deleteCapability(capability.name);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("DELETE");
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
});

describe("other methods", () => {
const serverCheck = {
adminState: "ONLINE",
Expand Down Expand Up @@ -312,6 +391,42 @@ describe("ServerService", () => {
req.flush({response});
await expectAsync(responseP).toBeResolvedTo(response);
});
it("queues revalidations on a server", async () => {
const responseP = service.queueReval(server);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("true");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("queues revalidations on a server by ID", async () => {
const responseP = service.queueReval(server.id);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("true");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("de-queues revalidations on a server", async () => {
const responseP = service.clearReval(server);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("false");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("de-queues revalidations on a server by ID", async () => {
const responseP = service.clearReval(server.id);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("false");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("sends a request for multiple Serverchecks", async () => {
const responseP = service.getServerChecks();
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/servercheck`);
Expand Down
43 changes: 43 additions & 0 deletions experimental/traffic-portal/src/app/api/server.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,49 @@ export class ServerService extends APIService {
return this.post<ServerQueueResponse>(`servers/${id}/queue_update`, {action: "dequeue"}).toPromise();
}

/**
* Queues revalidations on a single server.
*
* @param server Either the server on which revalidations will be queued, or
* its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async queueReval(server: number | ResponseServer): Promise<void> {
const id = typeof(server) === "number" ? server : server.id;
const params = {
// This param casing is in the API specification, so it must have
// this casing.
// eslint-disable-next-line @typescript-eslint/naming-convention
reval_updated: true
// TODO: This is really confusing; `reval_updated = true` means that
// revalidations **haven't** been updated, and need to be done.
};
return this.post(`servers/${id}/update`, undefined, params).toPromise();
}

/**
* Clears pending revalidations on a single server.
*
* @param server Either the server for which pending revalidations will be
* cleared, or its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async clearReval(server: number | ResponseServer): Promise<void> {
const id = typeof(server) === "number" ? server : server.id;
const params = {
// This param casing is in the API specification, so it must have
// this casing.
// eslint-disable-next-line @typescript-eslint/naming-convention
reval_updated: false
// TODO: This is really confusing; `reval_updated = false` means
// that revalidations **have** been updated, and no longer need to
// be done.
};
return this.post(`servers/${id}/update`, undefined, params).toPromise();
}

/**
* Updates a server's status.
*
Expand Down
36 changes: 36 additions & 0 deletions experimental/traffic-portal/src/app/api/testing/server.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,42 @@ export class ServerService {
return {action: "dequeue", serverId: id};
}

/**
* Queues revalidations on a single server.
*
* @param server Either the server on which revalidations will be queued, or
* its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async queueReval(server: number | ResponseServer): Promise<void> {
const id = typeof(server) === "number" ? server : server.id;
const srv = this.servers.find(s=>s.id===id);
if (!srv) {
throw new Error(`no such Server: #${id}`);
}

srv.revalPending = true;
}

/**
* Clears pending revalidations on a single server.
*
* @param server Either the server for which pending revalidations will be
* cleared, or its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async clearReval(server: number | ResponseServer): Promise<void> {
const id = typeof(server) === "number" ? server : server.id;
const srv = this.servers.find(s=>s.id===id);
if (!srv) {
throw new Error(`no such Server: #${id}`);
}

srv.revalPending = false;
}

/**
* Updates a server's status.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,22 +274,61 @@ describe("ServersTableComponent", () => {
const service = TestBed.inject(ServerService);
const queueSpy = spyOn(service, "queueUpdates");
const clearSpy = spyOn(service, "clearUpdates");
const revalSpy = spyOn(service, "queueReval");
const clearRevalSpy = spyOn(service, "clearReval");

expect(queueSpy).not.toHaveBeenCalled();
expect(clearSpy).not.toHaveBeenCalled();
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "queue", data: server});
expect(queueSpy).toHaveBeenCalledTimes(1);
expect(clearSpy).not.toHaveBeenCalled();
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "queue", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).not.toHaveBeenCalled();
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "dequeue", data: server});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(1);
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "dequeue", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "reval", data: server});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(1);
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "reval", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(3);
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "unreval", data: server});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(3);
expect(clearRevalSpy).toHaveBeenCalledTimes(1);

component.handleContextMenu({action: "unreval", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(3);
expect(clearRevalSpy).toHaveBeenCalledTimes(3);

expectAsync(component.handleContextMenu({action: "not a real action", data: []})).toBeRejected();
}));
Expand Down
Loading