mirror of https://github.com/status-im/js-waku.git
Merge pull request #1711 from waku-org/adklempner/validate-content-topics
feat: add function to validate autoshard content topic
This commit is contained in:
commit
5e9c981e60
|
@ -7,6 +7,8 @@
|
||||||
"ahadns",
|
"ahadns",
|
||||||
"Alives",
|
"Alives",
|
||||||
"asym",
|
"asym",
|
||||||
|
"autoshard",
|
||||||
|
"autosharding",
|
||||||
"backoff",
|
"backoff",
|
||||||
"backoffs",
|
"backoffs",
|
||||||
"bitauth",
|
"bitauth",
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"reporterEnabled": "spec, allure-mocha",
|
||||||
|
"allureMochaReporter": {
|
||||||
|
"outputDir": "allure-results"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
const config = {
|
||||||
|
extension: ['ts'],
|
||||||
|
spec: 'src/**/*.spec.ts',
|
||||||
|
require: ['ts-node/register', 'isomorphic-fetch'],
|
||||||
|
loader: 'ts-node/esm',
|
||||||
|
'node-option': [
|
||||||
|
'experimental-specifier-resolution=node',
|
||||||
|
'loader=ts-node/esm'
|
||||||
|
],
|
||||||
|
exit: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.CI) {
|
||||||
|
console.log("Running tests in parallel");
|
||||||
|
config.parallel = true;
|
||||||
|
config.jobs = 6;
|
||||||
|
console.log("Activating allure reporting");
|
||||||
|
config.reporter = 'mocha-multi-reporters';
|
||||||
|
config.reporterOptions = {
|
||||||
|
configFile: '.mocha.reporters.json'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.log("Running tests serially. To enable parallel execution update mocha config");
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config;
|
|
@ -59,7 +59,9 @@
|
||||||
"check:spelling": "cspell \"{README.md,src/**/*.ts}\"",
|
"check:spelling": "cspell \"{README.md,src/**/*.ts}\"",
|
||||||
"check:tsc": "tsc -p tsconfig.dev.json",
|
"check:tsc": "tsc -p tsconfig.dev.json",
|
||||||
"prepublish": "npm run build",
|
"prepublish": "npm run build",
|
||||||
"reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build"
|
"reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build",
|
||||||
|
"test": "run-s test:*",
|
||||||
|
"test:node": "TS_NODE_PROJECT=./tsconfig.dev.json mocha"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { ensureValidContentTopic } from "./sharding";
|
||||||
|
|
||||||
|
const testInvalidCases = (
|
||||||
|
contentTopics: string[],
|
||||||
|
expectedError: string
|
||||||
|
): void => {
|
||||||
|
for (const invalidTopic of contentTopics) {
|
||||||
|
expect(() => ensureValidContentTopic(invalidTopic)).to.throw(expectedError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("ensureValidContentTopic", () => {
|
||||||
|
it("does not throw on valid cases", () => {
|
||||||
|
const validTopics = [
|
||||||
|
"/0/myapp/1/mytopic/cbor",
|
||||||
|
"/myapp/1/mytopic/cbor",
|
||||||
|
"/myapp/v1.1/mytopic/cbor"
|
||||||
|
];
|
||||||
|
for (const validTopic of validTopics) {
|
||||||
|
expect(() => ensureValidContentTopic(validTopic)).to.not.throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("throws on empty content topic", () => {
|
||||||
|
testInvalidCases(["", " ", " "], "Content topic format is invalid");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on content topic with too few or too many fields", () => {
|
||||||
|
testInvalidCases(
|
||||||
|
[
|
||||||
|
"myContentTopic",
|
||||||
|
"myapp1mytopiccbor/",
|
||||||
|
" /myapp/1/mytopic",
|
||||||
|
"/myapp/1/mytopic",
|
||||||
|
"/0/myapp/1/mytopic/cbor/extra"
|
||||||
|
],
|
||||||
|
"Content topic format is invalid"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on content topic with non-number generation field", () => {
|
||||||
|
testInvalidCases(
|
||||||
|
[
|
||||||
|
"/a/myapp/1/mytopic/cbor",
|
||||||
|
"/ /myapp/1/mytopic/cbor",
|
||||||
|
"/_/myapp/1/mytopic/cbor",
|
||||||
|
"/$/myapp/1/mytopic/cbor"
|
||||||
|
],
|
||||||
|
"Invalid generation field in content topic"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note that this test case should be removed once Waku supports other generations
|
||||||
|
it("throws on content topic with generation field greater than 0", () => {
|
||||||
|
testInvalidCases(
|
||||||
|
[
|
||||||
|
"/1/myapp/1/mytopic/cbor",
|
||||||
|
"/2/myapp/1/mytopic/cbor",
|
||||||
|
"/3/myapp/1/mytopic/cbor",
|
||||||
|
"/1000/myapp/1/mytopic/cbor"
|
||||||
|
],
|
||||||
|
"Generation greater than 0 is not supported"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on content topic with empty application field", () => {
|
||||||
|
testInvalidCases(
|
||||||
|
["/0//1/mytopic/cbor"],
|
||||||
|
"Application field cannot be empty"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on content topic with empty version field", () => {
|
||||||
|
testInvalidCases(
|
||||||
|
["/0/myapp//mytopic/cbor"],
|
||||||
|
"Version field cannot be empty"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on content topic with empty topic name field", () => {
|
||||||
|
testInvalidCases(["/0/myapp/1//cbor"], "Topic name field cannot be empty");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on content topic with empty encoding field", () => {
|
||||||
|
testInvalidCases(["/0/myapp/1/mytopic/"], "Encoding field cannot be empty");
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,3 +18,42 @@ export function ensurePubsubTopicIsConfigured(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string, will throw an error if it is not formatted as a valid content topic for autosharding based on https://rfc.vac.dev/spec/51/
|
||||||
|
* @param contentTopic String to validate
|
||||||
|
*/
|
||||||
|
export function ensureValidContentTopic(contentTopic: string): void {
|
||||||
|
const parts = contentTopic.split("/");
|
||||||
|
if (parts.length < 5 || parts.length > 6) {
|
||||||
|
throw Error("Content topic format is invalid");
|
||||||
|
}
|
||||||
|
// Validate generation field if present
|
||||||
|
if (parts.length == 6) {
|
||||||
|
const generation = parseInt(parts[1]);
|
||||||
|
if (isNaN(generation)) {
|
||||||
|
throw new Error("Invalid generation field in content topic");
|
||||||
|
}
|
||||||
|
if (generation > 0) {
|
||||||
|
throw new Error("Generation greater than 0 is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Validate remaining fields
|
||||||
|
const fields = parts.splice(-4);
|
||||||
|
// Validate application field
|
||||||
|
if (fields[0].length == 0) {
|
||||||
|
throw new Error("Application field cannot be empty");
|
||||||
|
}
|
||||||
|
// Validate version field
|
||||||
|
if (fields[1].length == 0) {
|
||||||
|
throw new Error("Version field cannot be empty");
|
||||||
|
}
|
||||||
|
// Validate topic name field
|
||||||
|
if (fields[2].length == 0) {
|
||||||
|
throw new Error("Topic name field cannot be empty");
|
||||||
|
}
|
||||||
|
// Validate encoding field
|
||||||
|
if (fields[3].length == 0) {
|
||||||
|
throw new Error("Encoding field cannot be empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue