From 82950f1585079d7c7b8802ab530d3b8647d53f57 Mon Sep 17 00:00:00 2001 From: Lars Kissel Date: Tue, 5 Oct 2021 15:35:50 +0200 Subject: [PATCH 1/4] [FEATURE] Enhance versionInfo middleware to serve sap-ui-version.json Enhance the UI5 Server middleware 'versionInfo' to also serve the transitive information in the sap-ui-version.json as it would be generated with the UI5 Builder. JIRA: CPOUI5FOUNDATION-299 --- lib/middleware/versionInfo.js | 88 ++- test/lib/server/main.js | 21 + test/lib/server/middleware/versionInfo.js | 632 ++++++++++++++++++++++ 3 files changed, 713 insertions(+), 28 deletions(-) create mode 100644 test/lib/server/middleware/versionInfo.js diff --git a/lib/middleware/versionInfo.js b/lib/middleware/versionInfo.js index 0b438f8b..e8b7e9fa 100644 --- a/lib/middleware/versionInfo.js +++ b/lib/middleware/versionInfo.js @@ -1,4 +1,8 @@ +const log = require("@ui5/logger").getLogger("server:middleware:versionInfo"); const createVersionInfoProcessor = require("@ui5/builder").processors.versionInfoGenerator; +const manifestCreator = require("@ui5/builder").processors.manifestCreator; + +const MANIFEST_JSON = "manifest.json"; /** * Creates and returns the middleware to create the version info as json object. @@ -10,37 +14,65 @@ const createVersionInfoProcessor = require("@ui5/builder").processors.versionInf * @returns {Function} Returns a server middleware closure. */ function createMiddleware({resources, tree: project}) { - return function versionInfo(req, res, next) { - resources.dependencies.byGlob("/resources/**/.library") - .then((resources) => { - resources.sort((a, b) => { - return a._project.metadata.name.localeCompare(b._project.metadata.name); + return async function versionInfo(req, res, next) { + try { + const dependencies = resources.dependencies; + const dotLibResources = await dependencies.byGlob("/resources/**/.library"); + + dotLibResources.sort((a, b) => { + return a._project.metadata.name.localeCompare(b._project.metadata.name); + }); + + const libraryInfosPromises = dotLibResources.map(async (dotLibResource) => { + const namespace = dotLibResource._project.metadata.namespace; + const manifestResources = await dependencies.byGlob(`/resources/${namespace}/**/${MANIFEST_JSON}`); + let libraryManifest = manifestResources.find((manifestResource) => { + return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`; }); - return createVersionInfoProcessor({ - options: { - rootProjectName: project.metadata.name, - rootProjectVersion: project.version, - libraryInfos: resources.map((dotLibResource) => { - return { - name: dotLibResource._project.metadata.name, - version: dotLibResource._project.version - }; - }) + const embeddedManifests = + manifestResources.filter((manifestResource) => manifestResource !== libraryManifest); + if (!libraryManifest) { + const extensions = "js,json,library,less,css,theming,theme,properties"; + const libResources = await dependencies.byGlob(`/resources/${namespace}/**/*.{${extensions}}`); + + try { + libraryManifest = await manifestCreator({ + libraryResource: dotLibResource, + namespace, + resources: libResources, + options: {} + }); + } catch (err) { + // if the manifest creation fails (e.g. because of a missing project dependency), + // the processing of the version info shouldn't be interrupted + log.warn(err.message); } - }); - }) - .then(([versionInfoResource]) => { - return versionInfoResource.getBuffer(); - }) - .then((versionInfoContent) => { - res.writeHead(200, { - "Content-Type": "application/json" - }); - res.end(versionInfoContent.toString()); - }) - .catch((err) => { - next(err); + } + return { + libraryManifest, + embeddedManifests, + name: dotLibResource._project.metadata.name, + version: dotLibResource._project.version + }; + }); + const libraryInfos = await Promise.all(libraryInfosPromises); + + const [versionInfoResource] = await createVersionInfoProcessor({ + options: { + rootProjectName: project.metadata.name, + rootProjectVersion: project.version, + libraryInfos + } + }); + const versionInfoContent = await versionInfoResource.getBuffer(); + + res.writeHead(200, { + "Content-Type": "application/json" }); + res.end(versionInfoContent.toString()); + } catch (err) { + next(err); + } }; } diff --git a/test/lib/server/main.js b/test/lib/server/main.js index 2b3f6e5a..4dd54dc8 100644 --- a/test/lib/server/main.js +++ b/test/lib/server/main.js @@ -1,14 +1,23 @@ const test = require("ava"); +const sinon = require("sinon"); const supertest = require("supertest"); const ui5Server = require("../../../"); const server = ui5Server.server; const normalizer = require("@ui5/project").normalizer; +const logger = require("@ui5/logger"); let request; let serve; // Start server before running tests test.before((t) => { + t.context.versionInfoLogger = { + warn: sinon.stub() + }; + sinon.stub(logger, "getLogger") + .callThrough() + .withArgs("server:middleware:versionInfo").returns(t.context.versionInfoLogger); + return normalizer.generateProjectTree({ cwd: "./test/fixtures/application.a" }).then((tree) => { @@ -22,6 +31,7 @@ test.before((t) => { }); test.after.always(() => { + sinon.restore(); return new Promise((resolve, reject) => { serve.close((error) => { if (error) { @@ -212,6 +222,17 @@ test("Get sap-ui-version.json from versionInfo middleware (/resources/sap-ui-ver } t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); + const {versionInfoLogger} = t.context; + t.is(versionInfoLogger.warn.callCount, 4); + t.is(versionInfoLogger.warn.getCall(0).args[0], + "Couldn't find version for library 'sap.ui.core', project dependency missing?"); + t.is(versionInfoLogger.warn.getCall(1).args[0], + "Couldn't find version for library 'sap.ui.core', project dependency missing?"); + t.is(versionInfoLogger.warn.getCall(2).args[0], + "Couldn't find version for library 'sap.ui.core', project dependency missing?"); + t.is(versionInfoLogger.warn.getCall(3).args[0], + "Couldn't find version for library 'sap.ui.core', project dependency missing?"); + const buildTimestamp = res.body.buildTimestamp; t.deepEqual(res.body, { "name": "application.a", diff --git a/test/lib/server/middleware/versionInfo.js b/test/lib/server/middleware/versionInfo.js new file mode 100644 index 00000000..048b1b43 --- /dev/null +++ b/test/lib/server/middleware/versionInfo.js @@ -0,0 +1,632 @@ +const test = require("ava"); +const path = require("path"); +const ui5Fs = require("@ui5/fs"); +const resourceFactory = ui5Fs.resourceFactory; +const versionInfoMiddleware = require("../../../../lib/middleware/versionInfo"); + +function createWorkspace() { + return resourceFactory.createAdapter({ + virBasePath: "/", + project: { + metadata: { + name: "test.lib" + }, + version: "2.0.0", + dependencies: [ + { + metadata: { + name: "sap.ui.core" + }, + version: "1.0.0" + } + ] + } + }); +} + +/** + * + * @param {string[]} names e.g. ["lib", "a"] + * @returns {{metadata: {name, namespace}}} + */ +const createProjectMetadata = (names) => { + return { + metadata: { + name: names.join("."), + namespace: names.join("/") + } + }; +}; + +/** + * @param {module:@ui5/fs.DuplexCollection} dependencies + * @param {module:@ui5/fs.resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @returns {Promise} + */ +async function createDotLibrary(dependencies, resourceFactory, names) { + await dependencies.write(resourceFactory.createResource({ + path: `/resources/${names.join("/")}/.library`, + string: ` + + + ${names.join(".")} + SAP SE + + 2.0.0 + + Library ${names.slice(1).join(".").toUpperCase()} + + `, + project: createProjectMetadata(names) + })); +} + +/** + * + * @param {module:@ui5/fs.DuplexCollection} dependencies + * @param {module:@ui5/fs.resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @param {object[]} deps + * @param {string[]} [embeds] + * @param {string} [embeddedBy] + * @returns {Promise} + */ +const createManifestResource = async (dependencies, resourceFactory, names, deps, embeds, embeddedBy) => { + const content = { + "sap.app": { + "id": names.join("."), + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }; + + const libs = {}; + deps.forEach((dep) => { + libs[dep.name] = { + "minVersion": "1.84.0" + }; + if (dep.lazy) { + libs[dep.name].lazy = true; + } + }); + content["sap.ui5"]["dependencies"]["libs"] = libs; + if (embeds !== undefined) { + content["sap.app"]["embeds"] = embeds; + } + if (embeddedBy !== undefined) { + content["sap.app"]["embeddedBy"] = embeddedBy; + } + await dependencies.write(resourceFactory.createResource({ + path: `/resources/${names.join("/")}/manifest.json`, + string: JSON.stringify(content, null, 2), + project: createProjectMetadata(names) + })); +}; + +/** + * + * @param {module:@ui5/fs.DuplexCollection} dependencies + * @param {module:@ui5/fs.resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @param {object[]} deps + * @param {string[]} [embeds] + */ +const createResources = async (dependencies, resourceFactory, names, deps, embeds) => { + await createDotLibrary(dependencies, resourceFactory, names); + await createManifestResource(dependencies, resourceFactory, names, deps, embeds); +}; + +function createDependencies(oOptions = { + virBasePath: "/resources" +}) { + oOptions = Object.assign(oOptions, { + project: { + metadata: { + name: "test.lib3" + }, + version: "3.0.0"} + }); + return resourceFactory.createAdapter(oOptions); +} + +async function assertCreatedVersionInfo(t, expectedVersionInfo, versionInfoContent) { + const currentVersionInfo = JSON.parse(versionInfoContent); + + t.is(currentVersionInfo.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + + delete currentVersionInfo.buildTimestamp; // removing to allow deep comparison + currentVersionInfo.libraries.forEach((lib) => { + t.is(lib.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + delete lib.buildTimestamp; // removing to allow deep comparison + }); + + currentVersionInfo.libraries.sort((libraryA, libraryB) => { + return libraryA.name.localeCompare(libraryB.name); + }); + + t.deepEqual(currentVersionInfo, expectedVersionInfo, "Correct content"); +} + +test.serial("integration: sibling eager to lazy", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => + + // expected outcome + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], []); + + // create middleware + const resources = {dependencies}; + const tree = { + metadata: { + name: "myname" + }, + version: "1.33.7" + }; + const middleware = versionInfoMiddleware({resources, tree}); + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "" + }], + }, versionInfoContent); + } + }; + const next = function() { + t.fail("should not be called."); + }; + + await middleware(undefined, res, next); +}); + +test.serial("integration: sibling lazy to eager", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c (true) + // lib.b => lib.c + // lib.c => + + // expected outcome + // lib.a => lib.b, lib.c + // lib.b => lib.c + // lib.c => + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + await createResources(dependencies, resourceFactory, ["lib", "a"], + [{name: "lib.b"}, {name: "lib.c", lazy: true}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c"}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], []); + + // create middleware + const resources = {dependencies}; + const tree = { + metadata: { + name: "myname" + }, + version: "1.33.7" + }; + const middleware = versionInfoMiddleware({resources, tree}); + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {} + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "" + }], + }, versionInfoContent); + } + }; + const next = function() { + t.fail("should not be called."); + }; + + await middleware(undefined, res, next); +}); + +test.serial("integration: children eager to lazy", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b + // lib.b => lib.c (true) + // lib.c => + + // expected outcome + // lib.a => lib.b, lib.c (true) + // lib.b => lib.c (true) + // lib.c => + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + await createResources(dependencies, resourceFactory, ["lib", "a"], + [{name: "lib.b"}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], + [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], []); + + // create middleware + const resources = {dependencies}; + const tree = { + metadata: { + name: "myname" + }, + version: "1.33.7" + }; + const middleware = versionInfoMiddleware({resources, tree}); + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "" + }], + }, versionInfoContent); + } + }; + const next = function() { + t.fail("should not be called."); + }; + + await middleware(undefined, res, next); +}); + +test.serial("integration: children lazy to eager", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b (true) + // lib.b => lib.c + // lib.c => + + // expected outcome + // lib.a => lib.b (true), lib.c (true) + // lib.b => lib.c + // lib.c => + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + await createResources(dependencies, resourceFactory, ["lib", "a"], + [{name: "lib.b", lazy: true}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], + [{name: "lib.c"}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], []); + + // create middleware + const resources = {dependencies}; + const tree = { + metadata: { + name: "myname" + }, + version: "1.33.7" + }; + const middleware = versionInfoMiddleware({resources, tree}); + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + }, + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {} + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "" + }], + }, versionInfoContent); + } + }; + const next = function() { + t.fail("should not be called."); + }; + + await middleware(undefined, res, next); +}); + +test.serial("integration: Library with dependencies and subcomponent complex scenario", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => lib.d, lib.e (true) + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c + + // expected outcome + // lib.a => lib.b, lib.c, lib.d, lib.e + // lib.b => lib.c (true), lib.d (true), lib.e (true) + // lib.c => lib.d, lib.e + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}, {name: "lib.e", lazy: true}]); + + // lib.d + await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); + + // lib.e + await createResources(dependencies, resourceFactory, ["lib", "e"], []); + + // create middleware + const resources = {dependencies}; + const tree = { + metadata: { + name: "myname" + }, + version: "1.33.7" + }; + const middleware = versionInfoMiddleware({resources, tree}); + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.d", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {} + } + } + }, + }, + { + "name": "lib.e", + "scmRevision": "", + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + } + } + }, + }, versionInfoContent); + } + }; + const next = function() { + t.fail("should not be called."); + }; + + await middleware(undefined, res, next); +}); From 7934d00e722aa014f4f5d5feeb27ae8dc8ec3a72 Mon Sep 17 00:00:00 2001 From: Lars Kissel Date: Wed, 6 Oct 2021 13:59:40 +0200 Subject: [PATCH 2/4] [INTERNAL] Remove unnecessary versionInfo tests --- test/lib/server/middleware/versionInfo.js | 499 ++++------------------ 1 file changed, 83 insertions(+), 416 deletions(-) diff --git a/test/lib/server/middleware/versionInfo.js b/test/lib/server/middleware/versionInfo.js index 048b1b43..2be07965 100644 --- a/test/lib/server/middleware/versionInfo.js +++ b/test/lib/server/middleware/versionInfo.js @@ -1,5 +1,4 @@ const test = require("ava"); -const path = require("path"); const ui5Fs = require("@ui5/fs"); const resourceFactory = ui5Fs.resourceFactory; const versionInfoMiddleware = require("../../../../lib/middleware/versionInfo"); @@ -153,341 +152,7 @@ async function assertCreatedVersionInfo(t, expectedVersionInfo, versionInfoConte t.deepEqual(currentVersionInfo, expectedVersionInfo, "Correct content"); } -test.serial("integration: sibling eager to lazy", async (t) => { - const workspace = createWorkspace(); - await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - // input - // lib.a => lib.b, lib.c - // lib.b => lib.c (true) - // lib.c => - - // expected outcome - // lib.a => lib.b, lib.c - // lib.b => lib.c (true) - // lib.c => - - // dependencies - const dependencies = createDependencies({virBasePath: "/"}); - - // lib.a - await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}]); - - // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); - - // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], []); - - // create middleware - const resources = {dependencies}; - const tree = { - metadata: { - name: "myname" - }, - version: "1.33.7" - }; - const middleware = versionInfoMiddleware({resources, tree}); - - const res = { - writeHead: function() {}, - end: async function(versionInfoContent) { - await assertCreatedVersionInfo(t, { - "name": "myname", - "scmRevision": "", - "version": "1.33.7", - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.c": {} - } - } - }, - }, - { - "name": "lib.b", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.c", - "scmRevision": "" - }], - }, versionInfoContent); - } - }; - const next = function() { - t.fail("should not be called."); - }; - - await middleware(undefined, res, next); -}); - -test.serial("integration: sibling lazy to eager", async (t) => { - const workspace = createWorkspace(); - await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - // input - // lib.a => lib.b, lib.c (true) - // lib.b => lib.c - // lib.c => - - // expected outcome - // lib.a => lib.b, lib.c - // lib.b => lib.c - // lib.c => - - // dependencies - const dependencies = createDependencies({virBasePath: "/"}); - - // lib.a - await createResources(dependencies, resourceFactory, ["lib", "a"], - [{name: "lib.b"}, {name: "lib.c", lazy: true}]); - - // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c"}]); - - // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], []); - - // create middleware - const resources = {dependencies}; - const tree = { - metadata: { - name: "myname" - }, - version: "1.33.7" - }; - const middleware = versionInfoMiddleware({resources, tree}); - - const res = { - writeHead: function() {}, - end: async function(versionInfoContent) { - await assertCreatedVersionInfo(t, { - "name": "myname", - "scmRevision": "", - "version": "1.33.7", - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.c": {} - } - } - }, - }, - { - "name": "lib.b", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {} - } - } - }, - }, - { - "name": "lib.c", - "scmRevision": "" - }], - }, versionInfoContent); - } - }; - const next = function() { - t.fail("should not be called."); - }; - - await middleware(undefined, res, next); -}); - -test.serial("integration: children eager to lazy", async (t) => { - const workspace = createWorkspace(); - await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - // input - // lib.a => lib.b - // lib.b => lib.c (true) - // lib.c => - - // expected outcome - // lib.a => lib.b, lib.c (true) - // lib.b => lib.c (true) - // lib.c => - - // dependencies - const dependencies = createDependencies({virBasePath: "/"}); - - // lib.a - await createResources(dependencies, resourceFactory, ["lib", "a"], - [{name: "lib.b"}]); - - // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], - [{name: "lib.c", lazy: true}]); - - // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], []); - - // create middleware - const resources = {dependencies}; - const tree = { - metadata: { - name: "myname" - }, - version: "1.33.7" - }; - const middleware = versionInfoMiddleware({resources, tree}); - - const res = { - writeHead: function() {}, - end: async function(versionInfoContent) { - await assertCreatedVersionInfo(t, { - "name": "myname", - "scmRevision": "", - "version": "1.33.7", - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.c": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.b", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.c", - "scmRevision": "" - }], - }, versionInfoContent); - } - }; - const next = function() { - t.fail("should not be called."); - }; - - await middleware(undefined, res, next); -}); - -test.serial("integration: children lazy to eager", async (t) => { - const workspace = createWorkspace(); - await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - // input - // lib.a => lib.b (true) - // lib.b => lib.c - // lib.c => - - // expected outcome - // lib.a => lib.b (true), lib.c (true) - // lib.b => lib.c - // lib.c => - - // dependencies - const dependencies = createDependencies({virBasePath: "/"}); - - // lib.a - await createResources(dependencies, resourceFactory, ["lib", "a"], - [{name: "lib.b", lazy: true}]); - - // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], - [{name: "lib.c"}]); - - // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], []); - - // create middleware - const resources = {dependencies}; - const tree = { - metadata: { - name: "myname" - }, - version: "1.33.7" - }; - const middleware = versionInfoMiddleware({resources, tree}); - - const res = { - writeHead: function() {}, - end: async function(versionInfoContent) { - await assertCreatedVersionInfo(t, { - "name": "myname", - "scmRevision": "", - "version": "1.33.7", - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": { - "lazy": true - }, - "lib.c": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.b", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {} - } - } - }, - }, - { - "name": "lib.c", - "scmRevision": "" - }], - }, versionInfoContent); - } - }; - const next = function() { - t.fail("should not be called."); - }; - - await middleware(undefined, res, next); -}); - +// test case taken from: ui5-builder/test/lib/tasks/generateVersionInfo.js test.serial("integration: Library with dependencies and subcomponent complex scenario", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); @@ -539,89 +204,91 @@ test.serial("integration: Library with dependencies and subcomponent complex sce }; const middleware = versionInfoMiddleware({resources, tree}); - const res = { - writeHead: function() {}, - end: async function(versionInfoContent) { - await assertCreatedVersionInfo(t, { - "name": "myname", - "scmRevision": "", - "version": "1.33.7", - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.c": {}, - "lib.d": {}, - "lib.e": {} - } - } - }, - }, - { - "name": "lib.b", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": { - "lazy": true - }, - "lib.d": { - "lazy": true - }, - "lib.e": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.c", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.d": {}, - "lib.e": {} - } - } - }, - }, - { - "name": "lib.d", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.e": {} - } + const expectedVersionInfo = { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true } - }, - }, - { - "name": "lib.e", - "scmRevision": "", - }], - "components": { - "lib.a.sub.fold": { - "hasOwnPreload": true, - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": {} - } - } + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.d", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {} + } + } + }, + }, + { + "name": "lib.e", + "scmRevision": "", + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": {} } } - }, - }, versionInfoContent); + } + } + }, + }; + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, expectedVersionInfo, versionInfoContent); } }; const next = function() { From 1acf83a10385d66c6a86d07863f4c57b7cce4a1a Mon Sep 17 00:00:00 2001 From: Lars Kissel Date: Tue, 12 Oct 2021 16:29:55 +0200 Subject: [PATCH 3/4] [INTERNAL] Improve manifest creation, add unit test --- lib/middleware/versionInfo.js | 23 ++--- test/lib/server/main.js | 42 ++++----- test/lib/server/middleware/versionInfo.js | 102 +++++++++++++++++++++- 3 files changed, 129 insertions(+), 38 deletions(-) diff --git a/lib/middleware/versionInfo.js b/lib/middleware/versionInfo.js index e8b7e9fa..fc49e6c7 100644 --- a/lib/middleware/versionInfo.js +++ b/lib/middleware/versionInfo.js @@ -1,6 +1,5 @@ -const log = require("@ui5/logger").getLogger("server:middleware:versionInfo"); const createVersionInfoProcessor = require("@ui5/builder").processors.versionInfoGenerator; -const manifestCreator = require("@ui5/builder").processors.manifestCreator; +const createManifestProcessor = require("@ui5/builder").processors.manifestCreator; const MANIFEST_JSON = "manifest.json"; @@ -35,18 +34,14 @@ function createMiddleware({resources, tree: project}) { const extensions = "js,json,library,less,css,theming,theme,properties"; const libResources = await dependencies.byGlob(`/resources/${namespace}/**/*.{${extensions}}`); - try { - libraryManifest = await manifestCreator({ - libraryResource: dotLibResource, - namespace, - resources: libResources, - options: {} - }); - } catch (err) { - // if the manifest creation fails (e.g. because of a missing project dependency), - // the processing of the version info shouldn't be interrupted - log.warn(err.message); - } + libraryManifest = await createManifestProcessor({ + libraryResource: dotLibResource, + namespace, + resources: libResources, + options: { + omitMinVersions: true + } + }); } return { libraryManifest, diff --git a/test/lib/server/main.js b/test/lib/server/main.js index 4dd54dc8..9648a568 100644 --- a/test/lib/server/main.js +++ b/test/lib/server/main.js @@ -1,23 +1,14 @@ const test = require("ava"); -const sinon = require("sinon"); const supertest = require("supertest"); const ui5Server = require("../../../"); const server = ui5Server.server; const normalizer = require("@ui5/project").normalizer; -const logger = require("@ui5/logger"); let request; let serve; // Start server before running tests test.before((t) => { - t.context.versionInfoLogger = { - warn: sinon.stub() - }; - sinon.stub(logger, "getLogger") - .callThrough() - .withArgs("server:middleware:versionInfo").returns(t.context.versionInfoLogger); - return normalizer.generateProjectTree({ cwd: "./test/fixtures/application.a" }).then((tree) => { @@ -31,7 +22,6 @@ test.before((t) => { }); test.after.always(() => { - sinon.restore(); return new Promise((resolve, reject) => { serve.close((error) => { if (error) { @@ -222,17 +212,6 @@ test("Get sap-ui-version.json from versionInfo middleware (/resources/sap-ui-ver } t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); - const {versionInfoLogger} = t.context; - t.is(versionInfoLogger.warn.callCount, 4); - t.is(versionInfoLogger.warn.getCall(0).args[0], - "Couldn't find version for library 'sap.ui.core', project dependency missing?"); - t.is(versionInfoLogger.warn.getCall(1).args[0], - "Couldn't find version for library 'sap.ui.core', project dependency missing?"); - t.is(versionInfoLogger.warn.getCall(2).args[0], - "Couldn't find version for library 'sap.ui.core', project dependency missing?"); - t.is(versionInfoLogger.warn.getCall(3).args[0], - "Couldn't find version for library 'sap.ui.core', project dependency missing?"); - const buildTimestamp = res.body.buildTimestamp; t.deepEqual(res.body, { "name": "application.a", @@ -242,18 +221,39 @@ test("Get sap-ui-version.json from versionInfo middleware (/resources/sap-ui-ver "libraries": [ { name: "library.a", + manifestHints: { + dependencies: { + libs: { + "library.d": {} + } + } + }, version: "1.0.0", buildTimestamp, scmRevision: "" }, { name: "library.b", + manifestHints: { + dependencies: { + libs: { + "library.d": {} + } + } + }, version: "1.0.0", buildTimestamp, scmRevision: "" }, { name: "library.c", + manifestHints: { + dependencies: { + libs: { + "library.d": {} + } + } + }, version: "1.0.0", buildTimestamp, scmRevision: "" diff --git a/test/lib/server/middleware/versionInfo.js b/test/lib/server/middleware/versionInfo.js index 2be07965..99909608 100644 --- a/test/lib/server/middleware/versionInfo.js +++ b/test/lib/server/middleware/versionInfo.js @@ -1,7 +1,8 @@ const test = require("ava"); -const ui5Fs = require("@ui5/fs"); -const resourceFactory = ui5Fs.resourceFactory; -const versionInfoMiddleware = require("../../../../lib/middleware/versionInfo"); +const sinon = require("sinon"); +const mock = require("mock-require"); +const resourceFactory = require("@ui5/fs").resourceFactory; +let versionInfoMiddleware = require("../../../../lib/middleware/versionInfo"); function createWorkspace() { return resourceFactory.createAdapter({ @@ -152,6 +153,101 @@ async function assertCreatedVersionInfo(t, expectedVersionInfo, versionInfoConte t.deepEqual(currentVersionInfo, expectedVersionInfo, "Correct content"); } +test.beforeEach((t) => { + versionInfoMiddleware = mock.reRequire("../../../../lib/middleware/versionInfo"); +}); + +test.afterEach.always((t) => { + sinon.restore(); + mock.stopAll(); +}); + +test.serial("todo", async (t) => { + let stubCount = 0; + const manifestCreatorStub = sinon.stub().callsFake(() => `stubbed manifest ${stubCount++}`); + const dummyVersionInfo = resourceFactory.createResource({ + path: "/dummy/path", + string: "stubbed version info" + }); + const versionInfoGeneratorStub = sinon.stub().returns([dummyVersionInfo]); + mock("@ui5/builder", { + processors: { + manifestCreator: manifestCreatorStub, + versionInfoGenerator: versionInfoGeneratorStub + } + }); + versionInfoMiddleware = mock.reRequire("../../../../lib/middleware/versionInfo"); + + const dependencies = createDependencies({virBasePath: "/"}); + // create lib.a without manifest + await createDotLibrary(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}]); + // create lib.b with manifest: no manifestCreator call expected + await createResources(dependencies, resourceFactory, ["lib", "b"], []); + // create lib.c without manifest but with dummy files + await createDotLibrary(dependencies, resourceFactory, ["lib", "c"]); + [ + // relevant file extensions for manifest creation + "js", "json", "less", "css", "theming", "theme", "properties", + // other file extensions are irrelevant + "html", "txt", "ts" + ].forEach(async (extension) => { + await dependencies.write(resourceFactory.createResource({path: `/resources/lib/c/foo.${extension}`})); + }); + + const resources = {dependencies}; + const tree = { + metadata: { + name: "myname" + }, + version: "1.33.7" + }; + const middleware = versionInfoMiddleware({resources, tree}); + + const endStub = sinon.stub(); + await middleware( + /* req */ undefined, + /* res */ {writeHead: function() {}, end: endStub}, + /* next */ function() {}); + + t.is(manifestCreatorStub.callCount, 2); + t.is(manifestCreatorStub.getCall(0).args[0].libraryResource.getPath(), "/resources/lib/a/.library"); + t.is(manifestCreatorStub.getCall(1).args[0].libraryResource.getPath(), "/resources/lib/c/.library"); + t.is(manifestCreatorStub.getCall(0).args[0].namespace, "lib/a"); + t.is(manifestCreatorStub.getCall(1).args[0].namespace, "lib/c"); + t.deepEqual( + manifestCreatorStub.getCall(0).args[0].resources.map((resource) => resource.getPath()), + ["/resources/lib/a/.library"]); + t.deepEqual( + manifestCreatorStub.getCall(1).args[0].resources.map((resource) => resource.getPath()).sort(), + [ + "/resources/lib/c/.library", + "/resources/lib/c/foo.css", + "/resources/lib/c/foo.js", + "/resources/lib/c/foo.json", + "/resources/lib/c/foo.less", + "/resources/lib/c/foo.properties", + "/resources/lib/c/foo.theme", + "/resources/lib/c/foo.theming" + ]); + t.deepEqual(manifestCreatorStub.getCall(0).args[0].options, {omitMinVersions: true}); + t.deepEqual(manifestCreatorStub.getCall(1).args[0].options, {omitMinVersions: true}); + + t.is(versionInfoGeneratorStub.callCount, 1); + const versionInfoGeneratorOptions = versionInfoGeneratorStub.getCall(0).args[0].options; + t.is(versionInfoGeneratorOptions.rootProjectName, "myname"); + t.is(versionInfoGeneratorOptions.rootProjectVersion, "1.33.7"); + t.is(versionInfoGeneratorOptions.libraryInfos.length, 3); + t.is(versionInfoGeneratorOptions.libraryInfos[0].name, "lib.a"); + t.is(versionInfoGeneratorOptions.libraryInfos[0].libraryManifest, "stubbed manifest 0"); + t.is(versionInfoGeneratorOptions.libraryInfos[1].name, "lib.b"); + t.is(versionInfoGeneratorOptions.libraryInfos[1].libraryManifest.getPath(), "/resources/lib/b/manifest.json"); + t.is(versionInfoGeneratorOptions.libraryInfos[2].name, "lib.c"); + t.is(versionInfoGeneratorOptions.libraryInfos[2].libraryManifest, "stubbed manifest 1"); + + t.is(endStub.callCount, 1); + t.deepEqual(endStub.getCall(0).args[0], "stubbed version info"); +}); + // test case taken from: ui5-builder/test/lib/tasks/generateVersionInfo.js test.serial("integration: Library with dependencies and subcomponent complex scenario", async (t) => { const workspace = createWorkspace(); From 635710293c0f603b21630feafa1e83dac1f0551a Mon Sep 17 00:00:00 2001 From: Lars Kissel Date: Tue, 19 Oct 2021 13:20:43 +0200 Subject: [PATCH 4/4] [INTERNAL] Rename unit test --- test/lib/server/middleware/versionInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/server/middleware/versionInfo.js b/test/lib/server/middleware/versionInfo.js index 99909608..695d5ae6 100644 --- a/test/lib/server/middleware/versionInfo.js +++ b/test/lib/server/middleware/versionInfo.js @@ -162,7 +162,7 @@ test.afterEach.always((t) => { mock.stopAll(); }); -test.serial("todo", async (t) => { +test.serial("test all inner API calls within middleware", async (t) => { let stubCount = 0; const manifestCreatorStub = sinon.stub().callsFake(() => `stubbed manifest ${stubCount++}`); const dummyVersionInfo = resourceFactory.createResource({