diff --git a/packages/builder/lib/processors/versionInfoGenerator.js b/packages/builder/lib/processors/versionInfoGenerator.js index 21ff0d36c88..64f8b2a68ca 100644 --- a/packages/builder/lib/processors/versionInfoGenerator.js +++ b/packages/builder/lib/processors/versionInfoGenerator.js @@ -350,12 +350,57 @@ const processLibraryInfo = async (libraryInfo) => { * @typedef {object} LibraryInfo * @property {string} name The library name, e.g. "lib.x" * @property {string} version The library version, e.g. "1.0.0" + * @property {string} [id] The npm package id of the library project, e.g. "@openui5/sap.ui.core". + * Used by {@link resolveFramework} to detect the UI5 framework via npm-scope scanning. * @property {@ui5/fs/Resource} libraryManifest library manifest resource, * e.g. resource with path "lib/x/manifest.json" * @property {@ui5/fs/Resource[]} embeddedManifests list of embedded manifest resources, * e.g. resource with path "lib/x/sub/manifest.json" */ +/** + * Resolves the UI5 framework name and version from raw project inputs. + * + * Resolution order: + * 1. Explicit config: frameworkName is "OpenUI5" or "SAPUI5" + * → returns it with frameworkVersion coalesced to "". + * 2. Dependency scan: examines libraryInfos[].id for @sapui5/ or + * @openui5/ npm scopes; version comes from the sap.ui.core entry. + * 3. Fallback: {name: "", version: ""}. + * + * The returned object always has both name and version as strings. + * + * @public + * @param {string} [frameworkName] Framework name from the root project's ui5.yaml + * @param {string} [frameworkVersion] Framework version from the root project's ui5.yaml + * @param {module:@ui5/builder/processors/versionInfoGenerator~LibraryInfo[]} libraryInfos + * Library infos; each entry's optional id field is used for the npm-scope scan + * @returns {{name: string, version: string}} Resolved framework object + */ +export function resolveFramework(frameworkName, frameworkVersion, libraryInfos) { + if (frameworkName === "OpenUI5" || frameworkName === "SAPUI5") { + return {name: frameworkName, version: frameworkVersion || ""}; + } + let openui5Detected = false; + let openui5CoreVersion = ""; + for (const libraryInfo of libraryInfos) { + const {id} = libraryInfo; + if (id?.startsWith("@sapui5/")) { + return {name: "SAPUI5", version: ""}; + } + if (id?.startsWith("@openui5/")) { + openui5Detected = true; + if (libraryInfo.name === "sap.ui.core") { + openui5CoreVersion = libraryInfo.version; + } + } + } + if (openui5Detected) { + return {name: "OpenUI5", version: openui5CoreVersion}; + } + return {name: "", version: ""}; +} + /** * Creates sap-ui-version.json. * @@ -379,6 +424,11 @@ const processLibraryInfo = async (libraryInfo) => { * } * ``` * + * @param {string} [parameters.options.frameworkName] Framework name from the root project's ui5.yaml, + * e.g. "OpenUI5" or "SAPUI5". Used for framework resolution. + * @param {string} [parameters.options.frameworkVersion] Framework version from the root project's ui5.yaml, + * e.g. "1.136.0". Used only when frameworkName is recognized; + * defaults to "" when omitted. * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with an array containing the versionInfo resource */ @@ -463,6 +513,7 @@ export default async function({options}) { buildTimestamp: buildTimestamp, scmRevision: "", // TODO: insert current application scm revision here // gav: "", // TODO: insert current application id + version here + framework: resolveFramework(options.frameworkName, options.frameworkVersion, options.libraryInfos), libraries, components }; diff --git a/packages/builder/lib/tasks/generateVersionInfo.js b/packages/builder/lib/tasks/generateVersionInfo.js index 8cb2f8f6cfb..076f7408c46 100644 --- a/packages/builder/lib/tasks/generateVersionInfo.js +++ b/packages/builder/lib/tasks/generateVersionInfo.js @@ -41,7 +41,8 @@ export default async ({workspace, dependencies, options: {rootProject, pattern}} libraryManifest, embeddedManifests, name: dotLibResource.getProject().getName(), - version: dotLibResource.getProject().getVersion() + version: dotLibResource.getProject().getVersion(), + id: dotLibResource.getProject().getId?.() }; }); }); @@ -51,6 +52,8 @@ export default async ({workspace, dependencies, options: {rootProject, pattern}} options: { rootProjectName: rootProject.getName(), rootProjectVersion: rootProject.getVersion(), + frameworkName: rootProject.getFrameworkName?.(), + frameworkVersion: rootProject.getFrameworkVersion?.(), libraryInfos } }); diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json b/packages/builder/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json index 551fc7d927c..151d6d5be04 100644 --- a/packages/builder/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json @@ -3,5 +3,9 @@ "version": "1.0.0", "buildTimestamp": "202008120917", "scmRevision": "", + "framework": { + "name": "", + "version": "" + }, "libraries": [] } \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.m/dest/resources/sap-ui-version.json b/packages/builder/test/expected/build/application.m/dest/resources/sap-ui-version.json index a109feea58d..a965e2b6034 100644 --- a/packages/builder/test/expected/build/application.m/dest/resources/sap-ui-version.json +++ b/packages/builder/test/expected/build/application.m/dest/resources/sap-ui-version.json @@ -3,5 +3,9 @@ "version": "1.0.0", "buildTimestamp": "WILL_BE_IGNORED_BY_TESTS", "scmRevision": "", + "framework": { + "name": "", + "version": "" + }, "libraries": [] } diff --git a/packages/builder/test/expected/build/application.o/dest/resources/sap-ui-version.json b/packages/builder/test/expected/build/application.o/dest/resources/sap-ui-version.json index b6212fb25e3..38949352ec3 100644 --- a/packages/builder/test/expected/build/application.o/dest/resources/sap-ui-version.json +++ b/packages/builder/test/expected/build/application.o/dest/resources/sap-ui-version.json @@ -3,5 +3,9 @@ "version": "1.0.0", "buildTimestamp": "WILL_BE_IGNORED_BY_TESTS", "scmRevision": "", + "framework": { + "name": "", + "version": "" + }, "libraries": [] } diff --git a/packages/builder/test/lib/processors/versionInfoGenerator.js b/packages/builder/test/lib/processors/versionInfoGenerator.js index 5a69ac6c72d..3b0b541ec1b 100644 --- a/packages/builder/test/lib/processors/versionInfoGenerator.js +++ b/packages/builder/test/lib/processors/versionInfoGenerator.js @@ -1,6 +1,7 @@ import test from "ava"; import sinon from "sinon"; import esmock from "esmock"; +import {resolveFramework} from "../../../lib/processors/versionInfoGenerator.js"; test.beforeEach(async (t) => { t.context.warnLogStub = sinon.stub(); @@ -37,6 +38,13 @@ const assertVersionInfoContent = (t, oExpectedVersionInfo, sActualContent) => { delete lib.buildTimestamp; // removing to allow deep comparison }); + // The "framework" object is always emitted. When the expectation does not assert it + // explicitly, verify it defaults to empty strings and remove it to allow deep comparison. + if (!("framework" in oExpectedVersionInfo)) { + t.deepEqual(currentVersionInfo.framework, {name: "", version: ""}, + "framework defaults to empty strings when no framework option is passed"); + delete currentVersionInfo.framework; + } t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content"); }; @@ -59,6 +67,79 @@ test.serial("versionInfoGenerator empty libraryInfos parameter", async (t) => { }); +test.serial("versionInfoGenerator resolves framework from explicit frameworkName/frameworkVersion", async (t) => { + const {versionInfoGenerator} = t.context; + const versionInfos = await versionInfoGenerator({options: { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [], + frameworkName: "OpenUI5", frameworkVersion: "1.136.0"}}); + + const result = JSON.parse(await versionInfos[0].getString()); + t.deepEqual(result.framework, {name: "OpenUI5", version: "1.136.0"}); +}); + +// --- resolveFramework unit tests (pure function, no esmock needed) --- + +test("resolveFramework: explicit OpenUI5 returns name and version", (t) => { + t.deepEqual(resolveFramework("OpenUI5", "1.136.0", []), {name: "OpenUI5", version: "1.136.0"}); +}); + +test("resolveFramework: explicit SAPUI5 returns name and version", (t) => { + t.deepEqual(resolveFramework("SAPUI5", "1.136.0", []), {name: "SAPUI5", version: "1.136.0"}); +}); + +test("resolveFramework: name-only config coalesces missing version to empty string (fixes #3)", (t) => { + t.deepEqual(resolveFramework("SAPUI5", undefined, []), {name: "SAPUI5", version: ""}); + t.deepEqual(resolveFramework("OpenUI5", undefined, []), {name: "OpenUI5", version: ""}); +}); + +test("resolveFramework: explicit config wins over dependency scan", (t) => { + const libs = [{id: "@sapui5/sap.m", name: "sap.m", version: "1.136.0"}]; + t.deepEqual(resolveFramework("OpenUI5", "1.138.0", libs), {name: "OpenUI5", version: "1.138.0"}); +}); + +test("resolveFramework: unrecognized frameworkName falls through to scan and then fallback", (t) => { + t.deepEqual(resolveFramework("CustomFramework", "1.0.0", []), {name: "", version: ""}); +}); + +test("resolveFramework: scans @openui5/ dep and resolves OpenUI5 with sap.ui.core version", (t) => { + const libs = [ + {id: "@openui5/sap.m", name: "sap.m", version: "1.136.0"}, + {id: "@openui5/sap.ui.core", name: "sap.ui.core", version: "1.136.0"} + ]; + t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "OpenUI5", version: "1.136.0"}); +}); + +test("resolveFramework: scans @openui5/ dep without sap.ui.core yields empty version", (t) => { + const libs = [{id: "@openui5/sap.m", name: "sap.m", version: "1.136.0"}]; + t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "OpenUI5", version: ""}); +}); + +test("resolveFramework: scans @sapui5/ dep and resolves SAPUI5 with empty version", (t) => { + const libs = [{id: "@sapui5/sap.m", name: "sap.m", version: "1.136.0"}]; + t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "SAPUI5", version: ""}); +}); + +test("resolveFramework: @sapui5/ dep short-circuits before @openui5/ accumulation", (t) => { + const libs = [ + {id: "@openui5/sap.ui.core", name: "sap.ui.core", version: "1.136.0"}, + {id: "@sapui5/sap.m", name: "sap.m", version: "1.136.0"} + ]; + t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "SAPUI5", version: ""}); +}); + +test("resolveFramework: libs without id are ignored in scan (no-id fallback)", (t) => { + const libs = [ + {name: "sap.m", version: "1.136.0"}, + {id: undefined, name: "sap.ui.core", version: "1.136.0"} + ]; + t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "", version: ""}); +}); + +test("resolveFramework: empty libraryInfos returns fallback", (t) => { + t.deepEqual(resolveFramework(undefined, undefined, []), {name: "", version: ""}); +}); + + test.serial("versionInfoGenerator simple library infos", async (t) => { const {versionInfoGenerator} = t.context; const options = { diff --git a/packages/builder/test/lib/tasks/generateVersionInfo.js b/packages/builder/test/lib/tasks/generateVersionInfo.js index bedb63baf19..2c2adcfcd82 100644 --- a/packages/builder/test/lib/tasks/generateVersionInfo.js +++ b/packages/builder/test/lib/tasks/generateVersionInfo.js @@ -28,7 +28,10 @@ const createProjectMetadata = (names, version, type) => { getName: () => key, getNamespace: () => names.join("/"), getVersion: () => version || "3.0.0-" + key, - getType: () => type || "library" + getType: () => type || "library", + getId: () => key, + getFrameworkName: () => undefined, + getFrameworkVersion: () => undefined }; }; @@ -91,6 +94,14 @@ async function assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions) { return libraryA.name.localeCompare(libraryB.name); }); + // The "framework" object is always emitted. When the expectation does not assert it + // explicitly, verify it defaults to empty strings and remove it to allow deep comparison. + if (!("framework" in oExpectedVersionInfo)) { + t.deepEqual(currentVersionInfo.framework, {name: "", version: ""}, + "framework defaults to empty strings when no framework is resolved"); + delete currentVersionInfo.framework; + } + t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content"); } @@ -1184,3 +1195,175 @@ test.serial("integration: Library of type 'module'", async (t) => { t.is(infoLogStub.callCount, 0); }); + +test.serial("task: explicit OpenUI5 framework config is forwarded", async (t) => { + const workspace = createWorkspace(); + const rootProject = { + getName: () => "myname", + getVersion: () => "1.33.7", + getFrameworkName: () => "OpenUI5", + getFrameworkVersion: () => "1.136.0" + }; + const oOptions = { + workspace, + dependencies: resourceFactory.createAdapter({virBasePath: "/resources/"}), + options: { + pattern: "/resources/**/.library", + rootProject + } + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "framework": {"name": "OpenUI5", "version": "1.136.0"}, + "libraries": [] + }, oOptions); +}); + +test.serial("task: explicit SAPUI5 framework config is forwarded", async (t) => { + const workspace = createWorkspace(); + const rootProject = { + getName: () => "myname", + getVersion: () => "1.33.7", + getFrameworkName: () => "SAPUI5", + getFrameworkVersion: () => "1.136.0" + }; + const oOptions = { + workspace, + dependencies: resourceFactory.createAdapter({virBasePath: "/resources/"}), + options: { + pattern: "/resources/**/.library", + rootProject + } + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "framework": {"name": "SAPUI5", "version": "1.136.0"}, + "libraries": [] + }, oOptions); +}); + +test.serial("task: unrecognized framework name yields empty framework", async (t) => { + const workspace = createWorkspace(); + const rootProject = { + getName: () => "myname", + getVersion: () => "1.33.7", + getFrameworkName: () => "CustomFramework", + getFrameworkVersion: () => "1.0.0" + }; + const oOptions = { + workspace, + dependencies: resourceFactory.createAdapter({virBasePath: "/resources/"}), + options: { + pattern: "/resources/**/.library", + rootProject + } + }; + // framework not in expected → assertCreatedVersionInfo verifies it defaults to {name:"",version:""} + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [] + }, oOptions); +}); + +function createScopedDepProject(names, {id, version = "1.136.0"} = {}) { + const key = names.join("."); + return { + getName: () => key, + getNamespace: () => names.join("/"), + getVersion: () => version, + getType: () => "library", + getId: () => id ?? key, + getFrameworkName: () => undefined, + getFrameworkVersion: () => undefined + }; +} + +test.serial("task: scans @openui5/sap.ui.core dep and resolves OpenUI5 with core version", async (t) => { + const workspace = createWorkspace(); + const dep = resourceFactory.createAdapter({ + virBasePath: "/", + project: createScopedDepProject(["sap", "ui", "core"], {id: "@openui5/sap.ui.core", version: "1.136.0"}) + }); + await dep.write(resourceFactory.createResource({ + path: "/resources/sap/ui/core/.library", + string: `sap.ui.core` + })); + const rootProject = { + getName: () => "myapp", getVersion: () => "1.0.0", + getFrameworkName: () => undefined, getFrameworkVersion: () => undefined + }; + const oOptions = { + workspace, + dependencies: dep, + options: {pattern: "/resources/**/.library", rootProject} + }; + await assertCreatedVersionInfo(t, { + "name": "myapp", + "scmRevision": "", + "version": "1.0.0", + "framework": {"name": "OpenUI5", "version": "1.136.0"}, + "libraries": [{"name": "sap.ui.core", "scmRevision": "", "version": "1.136.0"}] + }, oOptions); +}); + +test.serial("task: scans @sapui5/ dep and resolves SAPUI5 with empty version", async (t) => { + const workspace = createWorkspace(); + const dep = resourceFactory.createAdapter({ + virBasePath: "/", + project: createScopedDepProject(["sap", "m"], {id: "@sapui5/sap.m", version: "1.136.0"}) + }); + await dep.write(resourceFactory.createResource({ + path: "/resources/sap/m/.library", + string: `sap.m` + })); + const rootProject = { + getName: () => "myapp", getVersion: () => "1.0.0", + getFrameworkName: () => undefined, getFrameworkVersion: () => undefined + }; + const oOptions = { + workspace, + dependencies: dep, + options: {pattern: "/resources/**/.library", rootProject} + }; + await assertCreatedVersionInfo(t, { + "name": "myapp", + "scmRevision": "", + "version": "1.0.0", + "framework": {"name": "SAPUI5", "version": ""}, + "libraries": [{"name": "sap.m", "scmRevision": "", "version": "1.136.0"}] + }, oOptions); +}); + +test.serial("task: explicit ui5.yaml config wins over @sapui5/ scan result", async (t) => { + const workspace = createWorkspace(); + const dep = resourceFactory.createAdapter({ + virBasePath: "/", + project: createScopedDepProject(["sap", "m"], {id: "@sapui5/sap.m", version: "1.136.0"}) + }); + await dep.write(resourceFactory.createResource({ + path: "/resources/sap/m/.library", + string: `sap.m` + })); + const rootProject = { + getName: () => "myapp", getVersion: () => "1.0.0", + getFrameworkName: () => "OpenUI5", getFrameworkVersion: () => "1.138.0" + }; + const oOptions = { + workspace, + dependencies: dep, + options: {pattern: "/resources/**/.library", rootProject} + }; + await assertCreatedVersionInfo(t, { + "name": "myapp", + "scmRevision": "", + "version": "1.0.0", + "framework": {"name": "OpenUI5", "version": "1.138.0"}, + "libraries": [{"name": "sap.m", "scmRevision": "", "version": "1.136.0"}] + }, oOptions); +}); diff --git a/packages/server/lib/middleware/versionInfo.js b/packages/server/lib/middleware/versionInfo.js index ac48ae70755..4fc12d94d1e 100644 --- a/packages/server/lib/middleware/versionInfo.js +++ b/packages/server/lib/middleware/versionInfo.js @@ -40,7 +40,8 @@ function createMiddleware({resources, middlewareUtil}) { libraryManifest, embeddedManifests, name: dotLibResource.getProject().getName(), - version: dotLibResource.getProject().getVersion() + version: dotLibResource.getProject().getVersion(), + id: dotLibResource.getProject().getId?.() }; }); const rootProject = middlewareUtil.getProject(); @@ -49,6 +50,8 @@ function createMiddleware({resources, middlewareUtil}) { options: { rootProjectName: rootProject.getName(), rootProjectVersion: rootProject.getVersion(), + frameworkName: rootProject.getFrameworkName?.(), + frameworkVersion: rootProject.getFrameworkVersion?.(), libraryInfos } }); diff --git a/packages/server/test/lib/server/main.js b/packages/server/test/lib/server/main.js index 54c9049be62..0633d6e2d0d 100644 --- a/packages/server/test/lib/server/main.js +++ b/packages/server/test/lib/server/main.js @@ -208,6 +208,7 @@ test("Get sap-ui-version.json from versionInfo middleware (/resources/sap-ui-ver "version": "1.0.0", "buildTimestamp": buildTimestamp, "scmRevision": "", + "framework": {"name": "", "version": ""}, "libraries": [ { name: "library.a", diff --git a/packages/server/test/lib/server/middleware/versionInfo.js b/packages/server/test/lib/server/middleware/versionInfo.js index e3ca031ee95..80e2d4b776c 100644 --- a/packages/server/test/lib/server/middleware/versionInfo.js +++ b/packages/server/test/lib/server/middleware/versionInfo.js @@ -32,7 +32,9 @@ const createProjectMetadata = (names, version, type) => { getName: () => key, getNamespace: () => names.join("/"), getVersion: () => version || "3.0.0-" + key, - getType: () => type || "library" + getType: () => type || "library", + getFrameworkName: () => undefined, + getFrameworkVersion: () => undefined }; }; @@ -272,6 +274,10 @@ test.serial("test all inner API calls within middleware", async (t) => { const versionInfoGeneratorOptions = versionInfoGeneratorStub.getCall(0).args[0].options; t.is(versionInfoGeneratorOptions.rootProjectName, "myname"); t.is(versionInfoGeneratorOptions.rootProjectVersion, "1.33.7"); + t.is(versionInfoGeneratorOptions.frameworkName, undefined, + "frameworkName forwarded from root project (undefined when not configured)"); + t.is(versionInfoGeneratorOptions.frameworkVersion, undefined, + "frameworkVersion forwarded from root project (undefined when not configured)"); t.is(versionInfoGeneratorOptions.libraryInfos.length, 3); t.is(versionInfoGeneratorOptions.libraryInfos[0].name, "lib.a"); t.is(await versionInfoGeneratorOptions.libraryInfos[0].libraryManifest.getString(), "mocked manifest.json 1"); @@ -349,6 +355,7 @@ test.serial("integration: Library with dependencies and subcomponent complex sce "name": "myname", "scmRevision": "", "version": "1.33.7", + "framework": {"name": "", "version": ""}, "libraries": [{ "name": "lib.a", "scmRevision": "", @@ -443,3 +450,98 @@ test.serial("integration: Library with dependencies and subcomponent complex sce await middleware(undefined, res, next); }); + +test.serial("integration: middleware resolves framework for configured OpenUI5 project", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + const coreProject = { + getName: () => "sap.ui.core", + getNamespace: () => "sap/ui/core", + getVersion: () => "1.136.0", + getType: () => "library", + getFrameworkName: () => undefined, + getFrameworkVersion: () => undefined, + getId: () => "@openui5/sap.ui.core" + }; + const dep = resourceFactory.createAdapter({virBasePath: "/", project: coreProject}); + coreProject.getSourceReader = () => dep; + await createDotLibrary(dep, resourceFactory, ["sap", "ui", "core"], "1.136.0"); + await createManifestResource(dep, resourceFactory, ["sap", "ui", "core"], []); + + const resources = { + dependencies: resourceFactory.createReaderCollection({ + name: "dependencies", + readers: [dep] + }) + }; + + const middlewareUtil = { + getProject: () => ({ + getName: () => "myapp", + getVersion: () => "1.0.0", + getFrameworkName: () => "OpenUI5", + getFrameworkVersion: () => "1.136.0" + }) + }; + + const versionInfoMiddleware = await t.context.createVersionInfoMiddleware(); + const middleware = versionInfoMiddleware({resources, middlewareUtil}); + + const expectedVersionInfo = { + "name": "myapp", + "scmRevision": "", + "version": "1.0.0", + "framework": {"name": "OpenUI5", "version": "1.136.0"}, + "libraries": [{"name": "sap.ui.core", "scmRevision": "", "version": "1.136.0"}] + }; + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, expectedVersionInfo, versionInfoContent); + } + }; + await middleware(undefined, res, function(err) { + throw err; + }); +}); + +test.serial("integration: middleware emits empty string version for name-only framework config", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + const resources = { + dependencies: resourceFactory.createReaderCollection({name: "dependencies", readers: []}) + }; + + const middlewareUtil = { + getProject: () => ({ + getName: () => "myapp", + getVersion: () => "1.0.0", + getFrameworkName: () => "SAPUI5", + getFrameworkVersion: () => undefined + }) + }; + + const versionInfoMiddleware = await t.context.createVersionInfoMiddleware(); + const middleware = versionInfoMiddleware({resources, middlewareUtil}); + + const expectedVersionInfo = { + "name": "myapp", + "scmRevision": "", + "version": "1.0.0", + "framework": {"name": "SAPUI5", "version": ""}, + "libraries": [] + }; + + const res = { + writeHead: function() {}, + end: async function(versionInfoContent) { + await assertCreatedVersionInfo(t, expectedVersionInfo, versionInfoContent); + } + }; + await middleware(undefined, res, function(err) { + throw err; + }); +});