diff --git a/lib/middleware/MiddlewareManager.js b/lib/middleware/MiddlewareManager.js index c9fea043..1c8073cd 100644 --- a/lib/middleware/MiddlewareManager.js +++ b/lib/middleware/MiddlewareManager.js @@ -1,5 +1,6 @@ import middlewareRepository from "./middlewareRepository.js"; import MiddlewareUtil from "./MiddlewareUtil.js"; +import logger from "@ui5/logger"; const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty); /** @@ -20,20 +21,22 @@ const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty); * @alias @ui5/server/internal/MiddlewareManager */ class MiddlewareManager { - constructor({graph, resources, options = { + constructor({graph, rootProject, resources, options = { sendSAPTargetCSP: false, serveCSPReports: false }}) { - if (!graph || !resources || !resources.all || !resources.rootProject || !resources.dependencies) { + if (!graph || !rootProject || !resources || !resources.all || + !resources.rootProject || !resources.dependencies) { throw new Error("[MiddlewareManager]: One or more mandatory parameters not provided"); } this.graph = graph; + this.rootProject = rootProject; this.resources = resources; this.options = options; this.middleware = Object.create(null); this.middlewareExecutionOrder = []; - this.middlewareUtil = new MiddlewareUtil(); + this.middlewareUtil = new MiddlewareUtil({graph, project: rootProject}); } /** @@ -275,19 +278,22 @@ class MiddlewareManager { await this.addMiddleware(middlewareDef.name, { customMiddleware: async ({resources, middlewareUtil}) => { const customMiddleware = this.graph.getExtension(middlewareDef.name); - const specVersion = customMiddleware.getSpecVersion(); - const options = { - configuration: middlewareDef.configuration + + const params = { + resources, + options: { + configuration: middlewareDef.configuration + } }; - const params = {resources, options}; - if ( - specVersion === "2.0" || specVersion === "2.1" || - specVersion === "2.2" || specVersion === "2.3" || - specVersion === "2.4" || specVersion === "2.5" || - specVersion === "2.6" - ) { - // Supply interface to MiddlewareUtil instance starting with specVersion 2.0 - params.middlewareUtil = middlewareUtil.getInterface(specVersion); + + const specVersion = customMiddleware.getSpecVersion(); + if (specVersion.gte("3.0")) { + params.options.middlewareName = middlewareDef.name; + params.log = logger.getGroupLogger(`server:custom-middleware:${middlewareDef.name}`); + } + const middlewareUtilInterface = middlewareUtil.getInterface(specVersion); + if (middlewareUtilInterface) { + params.middlewareUtil = middlewareUtilInterface; } return (await customMiddleware.getMiddleware())(params); }, diff --git a/lib/middleware/MiddlewareUtil.js b/lib/middleware/MiddlewareUtil.js index 48b3060a..5fa270b4 100644 --- a/lib/middleware/MiddlewareUtil.js +++ b/lib/middleware/MiddlewareUtil.js @@ -1,5 +1,12 @@ import parseurl from "parseurl"; import mime from "mime-types"; +import { + createReaderCollectionPrioritized, + createResource, + createFilterReader, + createLinkReader, + createFlatReader +} from "@ui5/fs/resourceFactory"; /** * Convenience functions for UI5 Server middleware. @@ -17,33 +24,21 @@ import mime from "mime-types"; */ class MiddlewareUtil { /** - * Get an interface to an instance of this class that only provides those functions - * that are supported by the given custom middleware extension specification version. * - * @param {string} specVersion Specification Version of custom middleware extension - * @returns {object} An object with bound instance methods supported by the given specification version + * @param {object} parameters + * @param {@ui5/project/graph/ProjectGraph} parameters.graph Relevant ProjectGraph + * @param {@ui5/project/specifications/Project} parameters.project Project that is being served + * @public */ - getInterface(specVersion) { - const baseInterface = { - getPathname: this.getPathname.bind(this), - getMimeInfo: this.getMimeInfo.bind(this) - }; - switch (specVersion) { - case "0.1": - case "1.0": - case "1.1": - return undefined; - case "2.0": - case "2.1": - case "2.2": - case "2.3": - case "2.4": - case "2.5": - case "2.6": - return baseInterface; - default: - throw new Error(`MiddlewareUtil: Unknown or unsupported Specification Version ${specVersion}`); + constructor({graph, project}) { + if (!graph) { + throw new Error(`Missing parameter "graph"`); + } + if (!project) { + throw new Error(`Missing parameter "project"`); } + this._graph = graph; + this._project = project; } /** @@ -100,6 +95,148 @@ class MiddlewareUtil { contentType: type + (charset ? "; charset=" + charset : "") }; } + /** + * Specification Version-dependent [Project]{@link @ui5/project/specifications/Project} interface. + * For details on individual functions, see [Project]{@link @ui5/project/specifications/Project} + * + * @public + * @typedef {object} @ui5/project/build/helpers/MiddlewareUtill~ProjectInterface + * @property {Function} getType Get the project type + * @property {Function} getName Get the project name + * @property {Function} getVersion Get the project version + * @property {Function} getNamespace Get the project namespace + * @property {Function} getRootReader Get the project rootReader + * @property {Function} getReader Get the project reader + * @property {Function} getCustomConfiguration Get the project Custom Configuration + * @property {Function} isFrameworkProject Check whether the project is a UI5-Framework project + */ + + /** + * Retrieve a single project from the dependency graph + * + *

+ * This method is only available to custom server middleware extensions defining + * Specification Version 3.0 and above. + * + * @param {string} [projectName] Name of the project to retrieve. Defaults to the project currently being built + * @returns {@ui5/project/build/helpers/MiddlewareUtill~ProjectInterface|undefined} + * project instance or undefined if the project is unknown to the graph + * @public + */ + getProject(projectName) { + if (projectName) { + return this._graph.getProject(projectName); + } + return this._project; + } + + /** + * Retrieve a list of direct dependencies of a given project from the dependency graph. + * Note that this list does not include transitive dependencies. + * + *

+ * This method is only available to custom server middleware extensions defining + * Specification Version 3.0 and above. + * + * @param {string} [projectName] Name of the project to retrieve. Defaults to the project currently being built + * @returns {string[]} Names of all direct dependencies + * @throws {Error} If the requested project is unknown to the graph + * @public + */ + getDependencies(projectName) { + return this._graph.getDependencies(projectName || this._project.getName()); + } + + /** + * Specification Version-dependent set of [@ui5/fs/resourceFactory]{@link @ui5/fs/resourceFactory} + * functions provided to middleware. + * For details on individual functions, see [@ui5/fs/resourceFactory]{@link @ui5/fs/resourceFactory} + * + * @public + * @typedef {object} @ui5/project/build/helpers/MiddlewareUtill~resourceFactory + * @property {Function} createResource Creates a [Resource]{@link @ui5/fs/Resource}. + * Accepts the same parameters as the [Resource]{@link @ui5/fs/Resource} constructor. + * @property {Function} createReaderCollectionPrioritized Creates a prioritized reader collection: + * [ReaderCollectionPrioritized]{@link @ui5/fs/ReaderCollectionPrioritized} + * @property {Function} createFilterReader + * Create a [Filter-Reader]{@link @ui5/fs/readers/Filter} with the given reader. + * @property {Function} createLinkReader + * Create a [Link-Reader]{@link @ui5/fs/readers/Filter} with the given reader. + * @property {Function} createFlatReader Create a [Link-Reader]{@link @ui5/fs/readers/Link} + * where all requests are prefixed with /resources/. + */ + + /** + * Provides limited access to [@ui5/fs/resourceFactory]{@link @ui5/fs/resourceFactory} functions + * + *

+ * This attribute is only available to custom server middleware extensions defining + * Specification Version 3.0 and above. + * + * @type {@ui5/project/build/helpers/MiddlewareUtill~resourceFactory} + * @public + */ + resourceFactory = { + createResource, + createReaderCollectionPrioritized, + createFilterReader, + createLinkReader, + createFlatReader, + }; + + /** + * Get an interface to an instance of this class that only provides those functions + * that are supported by the given custom middleware extension specification version. + * + * @param {@ui5/project/specifications/SpecificationVersion} specVersion + * SpecVersionComparator instance of the custom server middleware + * @returns {object} An object with bound instance methods supported by the given specification version + */ + getInterface(specVersion) { + if (specVersion.lt("2.0")) { + // Custom middleware defining specVersion <2.0 does not have access to any MiddlewareUtil API + return undefined; + } + + const baseInterface = {}; + bindFunctions(this, baseInterface, [ + "getPathname", "getMimeInfo" + ]); + + if (specVersion.gte("3.0")) { + // getProject function, returning an interfaced project instance + baseInterface.getProject = (projectName) => { + const project = this.getProject(projectName); + const baseProjectInterface = {}; + bindFunctions(project, baseProjectInterface, [ + "getType", "getName", "getVersion", "getNamespace", + "getRootReader", "getReader", "getCustomConfiguration", "isFrameworkProject" + ]); + return baseProjectInterface; + }; + // getDependencies function, returning an array of project names + baseInterface.getDependencies = (projectName) => { + return this.getDependencies(projectName); + }; + + baseInterface.resourceFactory = Object.create(null); + [ + // Once new functions get added, extract this array into a variable + // and enhance based on spec version once new functions get added + "createResource", "createReaderCollectionPrioritized", + "createFilterReader", "createLinkReader", "createFlatReader", + ].forEach((factoryFunction) => { + baseInterface.resourceFactory[factoryFunction] = this.resourceFactory[factoryFunction]; + }); + } + return baseInterface; + } +} + +function bindFunctions(sourceObject, targetObject, funcNames) { + funcNames.forEach((funcName) => { + targetObject[funcName] = sourceObject[funcName].bind(sourceObject); + }); } export default MiddlewareUtil; diff --git a/lib/middleware/serveResources.js b/lib/middleware/serveResources.js index 15b95fbe..c38e5de5 100644 --- a/lib/middleware/serveResources.js +++ b/lib/middleware/serveResources.js @@ -42,7 +42,7 @@ function createMiddleware({resources, middlewareUtil}) { let propertiesFileSourceEncoding = project?.getPropertiesFileSourceEncoding(); if (!propertiesFileSourceEncoding) { - if (project && ["0.1", "1.0", "1.1"].includes(project.getSpecVersion())) { + if (project && project.getSpecVersion().lte("1.1")) { // default encoding to "ISO-8859-1" for old specVersions propertiesFileSourceEncoding = "ISO-8859-1"; } else { diff --git a/lib/server.js b/lib/server.js index 3a35e713..f5780259 100644 --- a/lib/server.js +++ b/lib/server.js @@ -165,6 +165,7 @@ export async function serve(graph, { const middlewareManager = new MiddlewareManager({ graph, + rootProject, resources, options: { sendSAPTargetCSP, diff --git a/test/lib/server/middleware/MiddlewareManager.js b/test/lib/server/middleware/MiddlewareManager.js index 806d908a..548ff5a5 100644 --- a/test/lib/server/middleware/MiddlewareManager.js +++ b/test/lib/server/middleware/MiddlewareManager.js @@ -1,12 +1,30 @@ import test from "ava"; -import sinon from "sinon"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; import MiddlewareManager from "../../../../lib/middleware/MiddlewareManager.js"; import middlewareRepository from "../../../../lib/middleware/middlewareRepository.js"; +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.logger = { + getGroupLogger: sinon.stub().returns("group logger") + }; + + t.context.MiddlewareManager = await esmock("../../../../lib/middleware/MiddlewareManager.js", { + "@ui5/logger": t.context.logger + }); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + test("Missing parameters", (t) => { const err = t.throws(() => { new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: {} }); }); @@ -18,6 +36,7 @@ test("Correct parameters", (t) => { t.notThrows(() => { new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -28,8 +47,10 @@ test("Correct parameters", (t) => { }); test("applyMiddleware", async (t) => { + const {sinon} = t.context; const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "love", @@ -61,6 +82,7 @@ test("applyMiddleware", async (t) => { test("addMiddleware: Adding already added middleware produces unique middleware name", async (t) => { const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -97,6 +119,7 @@ test("addMiddleware: Adding already added middleware produces unique middleware test("addMiddleware: Adding middleware already added to middlewareExecutionOrder", async (t) => { const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -117,6 +140,7 @@ test("addMiddleware: Adding middleware already added to middlewareExecutionOrder test("addMiddleware: Add middleware", async (t) => { const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -140,6 +164,7 @@ test("addMiddleware: Add middleware", async (t) => { test("addMiddleware: Add middleware with beforeMiddleware and mountPath parameter", async (t) => { const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -166,6 +191,7 @@ test("addMiddleware: Add middleware with beforeMiddleware and mountPath paramete test("addMiddleware: Add middleware with afterMiddleware parameter", async (t) => { const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -192,6 +218,7 @@ test("addMiddleware: Add middleware with afterMiddleware parameter", async (t) = test("addMiddleware: Add middleware with invalid afterMiddleware parameter", async (t) => { const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -214,8 +241,10 @@ test("addMiddleware: Add middleware with invalid afterMiddleware parameter", asy }); test("addMiddleware: Add middleware with wrapperCallback parameter", async (t) => { + const {sinon} = t.context; const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -251,8 +280,10 @@ test("addMiddleware: Add middleware with wrapperCallback parameter", async (t) = }); test("addMiddleware: Add middleware with async wrapperCallback", async (t) => { + const {sinon} = t.context; const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -271,8 +302,10 @@ test("addMiddleware: Add middleware with async wrapperCallback", async (t) => { }); test("addStandardMiddleware: Adds standard middleware in correct order", async (t) => { + const {sinon} = t.context; const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -303,6 +336,7 @@ test("addStandardMiddleware: Adds standard middleware in correct order", async ( }); test("addCustomMiddleware: No custom middleware defined", async (t) => { + const {sinon} = t.context; const graph = { getRoot: () => { return { @@ -313,6 +347,7 @@ test("addCustomMiddleware: No custom middleware defined", async (t) => { }; const middlewareManager = new MiddlewareManager({ graph, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -326,6 +361,7 @@ test("addCustomMiddleware: No custom middleware defined", async (t) => { }); test("addCustomMiddleware: Custom middleware got added", async (t) => { + const {sinon} = t.context; const graph = { getRoot: () => { return { @@ -343,6 +379,7 @@ test("addCustomMiddleware: Custom middleware got added", async (t) => { }; const middlewareManager = new MiddlewareManager({ graph, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -375,6 +412,7 @@ test("addCustomMiddleware: Custom middleware got added", async (t) => { }); test("addCustomMiddleware: No special handling for custom middleware with duplicate name", async (t) => { + const {sinon} = t.context; const graph = { getRoot: () => { return { @@ -388,6 +426,7 @@ test("addCustomMiddleware: No special handling for custom middleware with duplic }; const middlewareManager = new MiddlewareManager({ graph, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -416,6 +455,7 @@ test("addCustomMiddleware: Missing name configuration", async (t) => { }; const middlewareManager = new MiddlewareManager({ graph, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -445,6 +485,7 @@ test("addCustomMiddleware: Both before- and afterMiddleware configuration", asyn }; const middlewareManager = new MiddlewareManager({ graph, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -473,6 +514,7 @@ test("addCustomMiddleware: Missing before- or afterMiddleware configuration", as }; const middlewareManager = new MiddlewareManager({ graph, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -489,10 +531,15 @@ test("addCustomMiddleware: Missing before- or afterMiddleware configuration", as }); test("addCustomMiddleware", async (t) => { + const {sinon} = t.context; const middlewareModuleStub = sinon.stub().returns("ok"); - const getSpecVersionStub = sinon.stub().returns("2.6"); + const specVersionGteStub = sinon.stub().returns(false); + const mockSpecificationVersion = { + toString: () => "2.6", + gte: specVersionGteStub + }; const getExtensionStub = sinon.stub().returns({ - getSpecVersion: getSpecVersionStub, + getSpecVersion: () => mockSpecificationVersion, getMiddleware: () => middlewareModuleStub }); const graph = { @@ -512,6 +559,7 @@ test("addCustomMiddleware", async (t) => { }; const middlewareManager = new MiddlewareManager({ graph, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -533,9 +581,12 @@ test("addCustomMiddleware", async (t) => { }); t.is(res, "ok", "Wrapper callback returned expected value"); + t.is(specVersionGteStub.callCount, 1, "SpecificationVersion#gte got called once"); + t.is(specVersionGteStub.getCall(0).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments"); t.is(middlewareUtil.getInterface.callCount, 1, "middlewareUtil.getInterface got called once"); - t.is(middlewareUtil.getInterface.getCall(0).args[0], "2.6", - "middlewareUtil.getInterface got called correct arguments"); + t.is(middlewareUtil.getInterface.getCall(0).args[0], mockSpecificationVersion, + "middlewareUtil.getInterface got called with correct arguments"); t.is(middlewareModuleStub.callCount, 1, "Middleware module got called once"); t.deepEqual(middlewareModuleStub.getCall(0).args[0], { resources: "resources", @@ -548,9 +599,82 @@ test("addCustomMiddleware", async (t) => { }, "Middleware module got called with correct arguments"); }); +test("addCustomMiddleware with specVersion 3.0", async (t) => { + const {sinon, MiddlewareManager} = t.context; + const middlewareModuleStub = sinon.stub().returns("ok"); + const specVersionGteStub = sinon.stub().returns(true); + const mockSpecificationVersion = { + toString: () => "3.0", + gte: specVersionGteStub + }; + const getExtensionStub = sinon.stub().returns({ + getSpecVersion: () => mockSpecificationVersion, + getMiddleware: () => middlewareModuleStub + }); + const graph = { + getRoot: () => { + return { + getName: () => "my project", + getCustomMiddleware: () => [{ + name: "my custom middleware A", + beforeMiddleware: "cors", + configuration: { + "🦊": "🐰" + } + }] + }; + }, + getExtension: getExtensionStub + }; + const middlewareManager = new MiddlewareManager({ + graph, + rootProject: "root project", + resources: { + all: "I", + rootProject: "like", + dependencies: "ponies" + } + }); + const addMiddlewareStub = sinon.stub(middlewareManager, "addMiddleware").resolves(); + await middlewareManager.addCustomMiddleware(); + + t.is(addMiddlewareStub.callCount, 1, "addMiddleware was called once"); + + const customMiddleware = addMiddlewareStub.getCall(0).args[1].customMiddleware; + const middlewareUtil = { + getInterface: sinon.stub().returns("interfacedMiddlewareUtil") + }; + const res = await customMiddleware({ + resources: "resources", + middlewareUtil + }); + + t.is(res, "ok", "Wrapper callback returned expected value"); + t.is(specVersionGteStub.callCount, 1, "SpecificationVersion#gte got called once"); + t.is(specVersionGteStub.getCall(0).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments"); + t.is(middlewareUtil.getInterface.callCount, 1, "middlewareUtil.getInterface got called once"); + t.is(middlewareUtil.getInterface.getCall(0).args[0], mockSpecificationVersion, + "middlewareUtil.getInterface got called with correct arguments"); + t.is(middlewareModuleStub.callCount, 1, "Middleware module got called once"); + t.deepEqual(middlewareModuleStub.getCall(0).args[0], { + resources: "resources", + options: { + configuration: { + "🦊": "🐰" + }, + middlewareName: "my custom middleware A" + }, + middlewareUtil: "interfacedMiddlewareUtil", + log: "group logger" + }, "Middleware module got called with correct arguments"); +}); + test("addStandardMiddleware: CSP middleware configured correctly (default)", async (t) => { + const {sinon} = t.context; const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -595,8 +719,10 @@ test("addStandardMiddleware: CSP middleware configured correctly (default)", asy }); test("addStandardMiddleware: CSP middleware configured correctly (enabled)", async (t) => { + const {sinon} = t.context; const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", @@ -653,8 +779,10 @@ test("addStandardMiddleware: CSP middleware configured correctly (enabled)", asy }); test("addStandardMiddleware: CSP middleware configured correctly (custom)", async (t) => { + const {sinon} = t.context; const middlewareManager = new MiddlewareManager({ graph: {}, + rootProject: "root project", resources: { all: "I", rootProject: "like", diff --git a/test/lib/server/middleware/MiddlewareUtil.js b/test/lib/server/middleware/MiddlewareUtil.js index ca89355b..0dc87d2f 100644 --- a/test/lib/server/middleware/MiddlewareUtil.js +++ b/test/lib/server/middleware/MiddlewareUtil.js @@ -3,17 +3,22 @@ import sinon from "sinon"; import esmock from "esmock"; import mime from "mime-types"; import MiddlewareUtil from "../../../../lib/middleware/MiddlewareUtil.js"; +import SpecificationVersion from "@ui5/project/specifications/SpecificationVersion"; test.afterEach.always((t) => { sinon.restore(); }); +function getSpecificationVersion(specVersion) { + return new SpecificationVersion(specVersion); +} + test.serial("getPathname", async (t) => { const parseurlStub = sinon.stub().returns({pathname: "path%20name"}); const MiddlewareUtil = await esmock("../../../../lib/middleware/MiddlewareUtil.js", { parseurl: parseurlStub }); - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); const pathname = middlewareUtil.getPathname("req"); t.is(parseurlStub.callCount, 1, "parseurl got called once"); @@ -22,7 +27,7 @@ test.serial("getPathname", async (t) => { }); test.serial("getMimeInfo", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); const lookupStub = sinon.stub(mime, "lookup").returns("mytype"); const charsetStub = sinon.stub(mime, "charset").returns("mycharset"); @@ -40,7 +45,7 @@ test.serial("getMimeInfo", (t) => { }); test.serial("getMimeInfo: unknown type", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); const lookupStub = sinon.stub(mime, "lookup"); const charsetStub = sinon.stub(mime, "charset"); @@ -57,18 +62,110 @@ test.serial("getMimeInfo: unknown type", (t) => { }, "Correct pathname returned"); }); +test("getProject", (t) => { + const getProjectStub = sinon.stub().returns("Pony farm!"); + const getProjectNameStub = sinon.stub().returns("root project name"); + const middlewareUtil = new MiddlewareUtil({ + graph: { + getProject: getProjectStub + }, + project: { + getName: getProjectNameStub + } + }); + + const res = middlewareUtil.getProject("pony farm"); + + t.is(getProjectStub.callCount, 1, "ProjectGraph#getProject got called once"); + t.is(getProjectStub.getCall(0).args[0], "pony farm", + "ProjectGraph#getProject got called with correct arguments"); + t.is(getProjectNameStub.callCount, 0, "#getName of root project has not been called"); + t.is(res, "Pony farm!", "Correct result"); +}); + +test("getProject: Default name", (t) => { + const getProjectStub = sinon.stub().returns("Pony farm!"); + const middlewareUtil = new MiddlewareUtil({ + graph: { + getProject: getProjectStub + }, + project: "root project" + }); + + const res = middlewareUtil.getProject(); + + t.is(getProjectStub.callCount, 0, "ProjectGraph#getProject never got called"); + t.is(res, "root project", "Correct result"); +}); + +test("getDependencies", (t) => { + const getDependenciesStub = sinon.stub().returns("Pony farm!"); + const getProjectNameStub = sinon.stub().returns("root project name"); + const middlewareUtil = new MiddlewareUtil({ + graph: { + getDependencies: getDependenciesStub + }, + project: { + getName: getProjectNameStub + } + }); + + const res = middlewareUtil.getDependencies("pony farm"); + + t.is(getDependenciesStub.callCount, 1, "ProjectGraph#getDependencies got called once"); + t.is(getDependenciesStub.getCall(0).args[0], "pony farm", + "ProjectGraph#getDependencies got called with correct arguments"); + t.is(getProjectNameStub.callCount, 0, "#getName of root project has not been called"); + t.is(res, "Pony farm!", "Correct result"); +}); + +test("getDependencies: Default name", (t) => { + const getDependenciesStub = sinon.stub().returns("Pony farm!"); + const getProjectNameStub = sinon.stub().returns("root project name"); + const middlewareUtil = new MiddlewareUtil({ + graph: { + getDependencies: getDependenciesStub + }, + project: { + getName: getProjectNameStub + } + }); + + const res = middlewareUtil.getDependencies(); + + t.is(getDependenciesStub.callCount, 1, "ProjectGraph#getDependencies got called once"); + t.is(getDependenciesStub.getCall(0).args[0], "root project name", + "ProjectGraph#getDependencies got called with correct arguments"); + t.is(getProjectNameStub.callCount, 1, "#getName of root project has been called once"); + t.is(res, "Pony farm!", "Correct result"); +}); + +test.serial("resourceFactory", (t) => { + const {resourceFactory} = new MiddlewareUtil({graph: "graph", project: "project"}); + t.is(typeof resourceFactory.createResource, "function", + "resourceFactory function createResource is available"); + t.is(typeof resourceFactory.createReaderCollectionPrioritized, "function", + "resourceFactory function createReaderCollectionPrioritized is available"); + t.is(typeof resourceFactory.createFilterReader, "function", + "resourceFactory function createFilterReader is available"); + t.is(typeof resourceFactory.createLinkReader, "function", + "resourceFactory function createLinkReader is available"); + t.is(typeof resourceFactory.createFlatReader, "function", + "resourceFactory function createFlatReader is available"); +}); + test("getInterface: specVersion 1.0", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("1.0"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("1.0")); t.is(interfacedMiddlewareUtil, undefined, "no interface provided"); }); test("getInterface: specVersion 2.0", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("2.0"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("2.0")); t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ "getPathname", @@ -80,9 +177,9 @@ test("getInterface: specVersion 2.0", (t) => { }); test("getInterface: specVersion 2.1", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("2.1"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("2.1")); t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ "getPathname", @@ -94,9 +191,9 @@ test("getInterface: specVersion 2.1", (t) => { }); test("getInterface: specVersion 2.2", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("2.2"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("2.2")); t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ "getPathname", @@ -108,9 +205,9 @@ test("getInterface: specVersion 2.2", (t) => { }); test("getInterface: specVersion 2.3", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("2.3"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("2.3")); t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ "getPathname", @@ -122,9 +219,9 @@ test("getInterface: specVersion 2.3", (t) => { }); test("getInterface: specVersion 2.4", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("2.4"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("2.4")); t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ "getPathname", @@ -136,9 +233,9 @@ test("getInterface: specVersion 2.4", (t) => { }); test("getInterface: specVersion 2.5", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("2.5"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("2.5")); t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ "getPathname", @@ -150,9 +247,9 @@ test("getInterface: specVersion 2.5", (t) => { }); test("getInterface: specVersion 2.6", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); - const interfacedMiddlewareUtil = middlewareUtil.getInterface("2.6"); + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("2.6")); t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ "getPathname", @@ -163,23 +260,94 @@ test("getInterface: specVersion 2.6", (t) => { t.is(typeof interfacedMiddlewareUtil.getMimeInfo, "function", "function getMimeInfo is provided"); }); -test("getInterface: specVersion undefined", (t) => { - const middlewareUtil = new MiddlewareUtil(); - - const err = t.throws(() => { - middlewareUtil.getInterface(); +test("getInterface: specVersion 3.0", (t) => { + const getProjectStub = sinon.stub().returns({ + getSpecVersion: () => "specVersion", + getType: () => "type", + getName: () => "name", + getVersion: () => "version", + getNamespace: () => "namespace", + getRootReader: () => "rootReader", + getReader: () => "reader", + getCustomConfiguration: () => "customConfiguration", + isFrameworkProject: () => "isFrameworkProject", + hasBuildManifest: () => "hasBuildManifest", // Should not be exposed + getFrameworkVersion: () => "frameworkVersion", // Should not be exposed }); + const getDependenciesStub = sinon.stub().returns(["dep a", "dep b"]); - t.is(err.message, "MiddlewareUtil: Unknown or unsupported Specification Version undefined", - "Throw with correct error message"); + const mockGraph = { + getProject: getProjectStub, + getDependencies: getDependenciesStub + }; + + const middlewareUtil = new MiddlewareUtil({graph: mockGraph, project: "project"}); + + const interfacedMiddlewareUtil = middlewareUtil.getInterface(getSpecificationVersion("3.0")); + + t.deepEqual(Object.keys(interfacedMiddlewareUtil), [ + "getPathname", + "getMimeInfo", + "getProject", + "getDependencies", + "resourceFactory", + ], "Correct methods are provided"); + + t.is(typeof interfacedMiddlewareUtil.getPathname, "function", "function getPathname is provided"); + t.is(typeof interfacedMiddlewareUtil.getMimeInfo, "function", "function getMimeInfo is provided"); + t.is(typeof interfacedMiddlewareUtil.getProject, "function", "function getProject is provided"); + t.is(typeof interfacedMiddlewareUtil.getDependencies, "function", "function getDependencies is provided"); + + // getProject + const interfacedProject = interfacedMiddlewareUtil.getProject("pony"); + t.deepEqual(Object.keys(interfacedProject), [ + "getType", + "getName", + "getVersion", + "getNamespace", + "getRootReader", + "getReader", + "getCustomConfiguration", + "isFrameworkProject", + ], "Correct methods are provided"); + + t.is(interfacedProject.getType(), "type", "getType function is bound correctly"); + t.is(interfacedProject.getName(), "name", "getName function is bound correctly"); + t.is(interfacedProject.getVersion(), "version", "getVersion function is bound correctly"); + t.is(interfacedProject.getNamespace(), "namespace", "getNamespace function is bound correctly"); + t.is(interfacedProject.getRootReader(), "rootReader", "getRootReader function is bound correctly"); + t.is(interfacedProject.getReader(), "reader", "getReader function is bound correctly"); + t.is(interfacedProject.getCustomConfiguration(), "customConfiguration", + "getCustomConfiguration function is bound correctly"); + t.is(interfacedProject.isFrameworkProject(), "isFrameworkProject", + "isFrameworkProject function is bound correctly"); + + // getDependencies + t.deepEqual(interfacedMiddlewareUtil.getDependencies("pony"), ["dep a", "dep b"], + "getDependencies function is available and bound correctly"); + + // resourceFactory + const resourceFactory = interfacedMiddlewareUtil.resourceFactory; + t.is(typeof resourceFactory.createResource, "function", + "resourceFactory function createResource is available"); + t.is(typeof resourceFactory.createReaderCollectionPrioritized, "function", + "resourceFactory function createReaderCollectionPrioritized is available"); + t.is(typeof resourceFactory.createFilterReader, "function", + "resourceFactory function createFilterReader is available"); + t.is(typeof resourceFactory.createLinkReader, "function", + "resourceFactory function createLinkReader is available"); + t.is(typeof resourceFactory.createFlatReader, "function", + "resourceFactory function createFlatReader is available"); }); test("getInterface: specVersion unknown", (t) => { - const middlewareUtil = new MiddlewareUtil(); + const middlewareUtil = new MiddlewareUtil({graph: "graph", project: "project"}); const err = t.throws(() => { - middlewareUtil.getInterface("1.5"); + middlewareUtil.getInterface(getSpecificationVersion("1.5")); }); - t.is(err.message, "MiddlewareUtil: Unknown or unsupported Specification Version 1.5", + t.is(err.message, + "Unsupported Specification Version 1.5 defined. Your UI5 CLI installation might be outdated. " + + "For details, see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions", "Throw with correct error message"); }); diff --git a/test/lib/server/middleware/serveIndex.js b/test/lib/server/middleware/serveIndex.js index a783fddb..884d3351 100644 --- a/test/lib/server/middleware/serveIndex.js +++ b/test/lib/server/middleware/serveIndex.js @@ -26,7 +26,7 @@ test.serial("serveIndex default", async (t) => { writeResource(readerWriter, "/.myFile4", Buffer.alloc(1024)), // hidden 1 KB ]); const middleware = serveIndexMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter } @@ -92,7 +92,7 @@ test.serial("serveIndex no hidden", async (t) => { writeResource(readerWriter, "/.myFile4", Buffer.alloc(1024)), // hidden 1 KB ]); const middleware = serveIndexMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter }, @@ -161,7 +161,7 @@ test.serial("serveIndex no details", async (t) => { writeResource(readerWriter, "/.myFile4", Buffer.alloc(1024)), // hidden 1 KB ]); const middleware = serveIndexMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter }, diff --git a/test/lib/server/middleware/serveResources.js b/test/lib/server/middleware/serveResources.js index 0edaeb77..7a43d7de 100644 --- a/test/lib/server/middleware/serveResources.js +++ b/test/lib/server/middleware/serveResources.js @@ -61,7 +61,7 @@ test.serial("Check if properties file is served properly", async (t) => { const setStringSpy = sinon.spy(resource, "setString"); const middleware = serveResourcesMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter } @@ -101,7 +101,7 @@ test.serial("Check if properties file is served properly with UTF-8", async (t) const setStringSpy = sinon.spy(resource, "setString"); const middleware = serveResourcesMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter } @@ -137,7 +137,7 @@ test.serial("Check if properties file is served properly without property settin ); const setStringSpy = sinon.spy(resource, "setString"); const middleware = serveResourcesMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter } @@ -171,14 +171,19 @@ test.serial("Check if properties file is served properly without property settin const readerWriter = resourceFactory.createAdapter({virBasePath: "/"}); const project = { getPropertiesFileSourceEncoding: () => "", - getSpecVersion: () => "1.1" + getSpecVersion: () => { + return { + toString: () => "1.1", + lte: () => true, + }; + } }; const resource = await writeResource(readerWriter, "/myFile3.properties", 1024 * 1024, "key=titel\nfame=straße", "latin1", project ); const setStringSpy = sinon.spy(resource, "setString"); const middleware = serveResourcesMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter } @@ -209,14 +214,19 @@ test.serial("Check if properties file is served properly without property settin const readerWriter = resourceFactory.createAdapter({virBasePath: "/"}); const project = { getPropertiesFileSourceEncoding: () => "", - getSpecVersion: () => "2.0" + getSpecVersion: () => { + return { + toString: () => "2.0", + lte: () => false, + }; + } }; const resource = await writeResource(readerWriter, "/myFile3.properties", 1024 * 1024, "key=titel\nfame=straße", "utf8", project ); const setStringSpy = sinon.spy(resource, "setString"); const middleware = serveResourcesMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources: { all: readerWriter } @@ -287,7 +297,7 @@ test.serial("Check verbose logging", async (t) => { } }; const middleware = serveResourcesMiddlewareWithMock({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources }); @@ -354,7 +364,7 @@ test.serial("Check if version replacement is done", (t) => { } }; const middleware = serveResourcesMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources }); @@ -433,7 +443,7 @@ test.serial("Check if utf8 characters are correctly processed in version replace } }; const middleware = serveResourcesMiddleware({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources }); diff --git a/test/lib/server/middleware/serveThemes.js b/test/lib/server/middleware/serveThemes.js index a58ddb64..deb58536 100644 --- a/test/lib/server/middleware/serveThemes.js +++ b/test/lib/server/middleware/serveThemes.js @@ -130,7 +130,7 @@ test.beforeEach(async (t) => { t.context.byPath = resources.all.byPath; t.context.middleware = t.context.serveThemes({ - middlewareUtil: new MiddlewareUtil(), + middlewareUtil: new MiddlewareUtil({graph: "graph", project: "project"}), resources }); });