Skip to content

Commit

Permalink
test(sdl): cover endpoints validation
Browse files Browse the repository at this point in the history
  • Loading branch information
ygrishajev committed May 10, 2024
1 parent 7057fa8 commit aece776
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 11 deletions.
101 changes: 101 additions & 0 deletions src/sdl/SDL/SDL.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,107 @@ describe("SDL", () => {
});
});

describe("endpoints", () => {
it("should resolve with valid endpoints", () => {
const endpointName = faker.lorem.word();
const endpoint = {
[endpointName]: {
kind: "ip"
}
};
const yml = readBasicSdl({ endpoint });
const sdl = SDL.fromString(yml, "beta3", "sandbox");

expect(sdl.manifest()).toMatchObject([
{
services: [
{
resources: {
endpoints: {
1: {
kind: 2,
sequence_number: 1
}
}
},
expose: [
{
ip: endpointName,
endpointSequenceNumber: 1
}
]
}
]
}
]);
expect(sdl.groups()).toMatchObject([
{
resources: [
{
resource: {
endpoints: {
1: {
kind: 2,
sequence_number: 1
}
}
}
}
]
}
]);
});

it("should throw provided an invalid endpoint name", () => {
const endpointName = faker.number.int().toString();
const endpoint = {
[endpointName]: {
kind: "ip"
}
};
const yml = readBasicSdl({ endpoint });

expect(() => SDL.fromString(yml, "beta3", "sandbox")).toThrowError(new SdlValidationError(`Endpoint named "${endpointName}" is not a valid name.`));
});

it("should throw provided no endpoint kind", () => {
const endpointName = faker.lorem.word();
const endpoint = {
[endpointName]: {}
};
const yml = readBasicSdl({ endpoint });

expect(() => SDL.fromString(yml, "beta3", "sandbox")).toThrowError(new SdlValidationError(`Endpoint named "${endpointName}" has no kind.`));
});

it("should throw provided invalid endpoint kind", () => {
const endpointName = faker.lorem.word();
const endpointKind = faker.lorem.word();
const endpoint = {
[endpointName]: {
kind: endpointKind
}
};
const yml = readBasicSdl({ endpoint });

expect(() => SDL.fromString(yml, "beta3", "sandbox")).toThrowError(
new SdlValidationError(`Endpoint named "${endpointName}" has an unknown kind "${endpointKind}".`)
);
});

it("should throw when endpoint is unused", () => {
const endpointName = faker.lorem.word();
const endpoint = {
[endpointName]: {
kind: "ip"
}
};
const yml = readBasicSdl({ endpoint, endpointRef: undefined });

expect(() => SDL.fromString(yml, "beta3", "sandbox")).toThrowError(new SdlValidationError(`Endpoint ${endpointName} declared but never used.`));
});
});

describe("service image credentials", () => {
it("should resolve a service with valid credentials", () => {
const credentials = {
Expand Down
21 changes: 15 additions & 6 deletions src/sdl/SDL/SDL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class SDL {
// to v2 or v3 SDL only after being validated
const data = YAML.load(yaml) as v3Sdl;

for (const [name, profile] of Object.entries(data.profiles.compute)) {
for (const [name, profile] of Object.entries(data.profiles.compute || {})) {
this.validateGPU(name, profile.resources.gpu);
this.validateStorage(name, profile.resources.storage);
}
Expand Down Expand Up @@ -157,7 +157,7 @@ export class SDL {
// TODO: this should really be cast to unknown, then assigned
// to v2 or v3 SDL only after being validated
const v3data = this.data as v3Sdl;
Object.entries(v3data.profiles.compute).forEach(([name, { resources }]) => {
Object.entries(v3data.profiles.compute || {}).forEach(([name, { resources }]) => {
if ("gpu" in resources) {
SDL.validateGPU(name, resources.gpu);
}
Expand All @@ -173,6 +173,7 @@ export class SDL {
});

this.validateDenom();
this.validateEndpointsUtility();
}

private validateDenom() {
Expand Down Expand Up @@ -215,12 +216,12 @@ export class SDL {

Object.keys(this.data.deployment[serviceName]).forEach(deploymentName => {
const serviceDeployment = this.data.deployment[serviceName][deploymentName];
const compute = this.data.profiles.compute[serviceDeployment.profile];
const infra = this.data.profiles.placement[deploymentName];
const compute = this.data.profiles.compute?.[serviceDeployment.profile];
const infra = this.data.profiles.placement?.[deploymentName];

SdlValidationError.assert(infra, `The placement "${deploymentName}" is not defined in the "placement" section.`);
SdlValidationError.assert(
infra.pricing[serviceDeployment.profile],
infra.pricing?.[serviceDeployment.profile],
`The pricing for the "${serviceDeployment.profile}" profile is not defined in the "${deploymentName}" "placement" definition.`
);
SdlValidationError.assert(compute, `The compute requirements for the "${serviceDeployment.profile}" profile are not defined in the "compute" section.`);
Expand Down Expand Up @@ -253,6 +254,14 @@ export class SDL {
});
}

private validateEndpointsUtility() {
if (this.data.endpoints) {
Object.keys(this.data.endpoints).forEach(endpoint => {
SdlValidationError.assert(this.endpointsUsed.has(endpoint), `Endpoint ${endpoint} declared but never used.`);
});
}
}

services() {
if (this.data) {
return this.data.services;
Expand Down Expand Up @@ -849,7 +858,7 @@ export class SDL {
const pricing = infra.pricing[svcdepl.profile];
const price = {
...pricing,
amount: pricing.amount.toString()
amount: pricing.amount?.toString()
};

let group = groups.get(placementName);
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/sdl-basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ services:
as: 80
to:
- global: true
${endpointRef}
${credentials}

profiles:
Expand All @@ -33,3 +34,5 @@ deployment:
dcloud:
profile: web
count: 1

${endpoints}
16 changes: 12 additions & 4 deletions test/yml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ export const readYml = (name: string): string => {
};

type YmlInputObject = {
[key: string]: string | number | boolean | null | YmlInputObject;
[key: string]: string | number | boolean | null | YmlInputObject | YmlInputObject[];
};

export const toYmlFragment = (object: YmlInputObject, options?: { nestingLevel: number }): string => {
const yamlString = dump(object);
const yamlString = dump(object, { forceQuotes: true, quotingType: '"' });

if (!options) {
return yamlString;
Expand All @@ -37,15 +37,23 @@ interface BasicSdlTemplateVariables {
username?: string;
password?: string;
};
endpoint?: Record<string, { kind?: string | "ip" }>;
endpointRef?: { ip: string };
}

const readBasicSdlTemplate = memoize(() => template(readYml("sdl-basic")));

export const readBasicSdl = (variables: BasicSdlTemplateVariables = {}): string => {
const createYML = readBasicSdlTemplate();
const ymlVars: Record<keyof BasicSdlTemplateVariables, string> = {
const endpointName = Object.keys(variables.endpoint || {})[0];
const ymlVars: Record<keyof Omit<BasicSdlTemplateVariables, "endpoint"> | "endpoints" | "endpointRef", string> = {
denom: variables.denom || faker.helpers.arrayElement([AKT_DENOM, USDC_IBC_DENOMS[SANDBOX_ID]]),
credentials: variables.credentials ? toYmlFragment(pick(variables, "credentials"), { nestingLevel: 2 }) : ""
credentials: variables.credentials ? toYmlFragment(pick(variables, "credentials"), { nestingLevel: 2 }) : "",
endpoints: variables.endpoint ? toYmlFragment({ endpoints: variables.endpoint }) : "",
endpointRef:
("endpointRef" in variables && toYmlFragment(pick(variables, "endpointRef"), { nestingLevel: 3 })) ||
(endpointName && toYmlFragment({ ip: endpointName })) ||
""
};

return createYML(ymlVars);
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["./src/**/*"],
"exclude": ["./examples", "./tests"]
"exclude": ["./examples", "./tests", "./test", "./src/**/*.spec.ts"]
}

0 comments on commit aece776

Please sign in to comment.