diff --git a/lib/projectPreprocessor.js b/lib/projectPreprocessor.js index 1b503e04d..fdcf7112e 100644 --- a/lib/projectPreprocessor.js +++ b/lib/projectPreprocessor.js @@ -283,12 +283,19 @@ class ProjectPreprocessor { throw new Error(`Root project needs to be of kind "project". ${project.id} is of kind ${project.kind}`); } - if (project.kind === "project" && project.type === "application" && project._level !== 0) { - // There is only one project project of type application allowed - // That project needs to be the root project - log.verbose(`[Warn] Ignoring project ${project.id} with type application`+ - ` (distance to root: ${project._level}). Type application is only allowed for the root project`); - return false; // ignore this project + if (project.kind === "project" && project.type === "application") { + // There must be exactly one application project per dependency tree + // If multiple are found, all but the one closest to the root are rejected (ignored) + // If there are two projects equally close to the root, an error is being thrown + if (!this.qualifiedApplicationProject) { + this.qualifiedApplicationProject = project; + } else if (this.qualifiedApplicationProject._level === project._level) { + throw new Error(`Found at least two projects ${this.qualifiedApplicationProject.id} and ` + + `${project.id} of type application with the same distance to the root project. ` + + "Only one project of type application can be used. Failed to decide which one to ignore."); + } else { + return false; // ignore this project + } } return true; diff --git a/test/lib/projectPreprocessor.js b/test/lib/projectPreprocessor.js index 20a31455d..d001d88cb 100644 --- a/test/lib/projectPreprocessor.js +++ b/test/lib/projectPreprocessor.js @@ -200,6 +200,143 @@ test("Missing dependencies", (t) => { "Gracefully accepted project with no dependency attribute"); }); +test("Single non-root application-project", (t) => { + const tree = ({ + id: "library.a", + version: "1.0.0", + path: libraryAPath, + dependencies: [{ + id: "application.a", + version: "1.0.0", + path: applicationAPath, + dependencies: [] + }] + }); + return projectPreprocessor.processTree(tree).then((parsedTree) => { + t.deepEqual(parsedTree.id, "library.a", "Correct root project"); + t.deepEqual(parsedTree.dependencies.length, 1, "application-project dependency was not ignored"); + t.deepEqual(parsedTree.dependencies[0].id, "application.a", "application-project is on second level"); + }); +}); + +test("Multiple non-root application-projects on same level", (t) => { + const tree = ({ + id: "library.a", + version: "1.0.0", + path: libraryAPath, + dependencies: [{ + id: "application.a", + version: "1.0.0", + path: applicationAPath, + dependencies: [] + }, { + id: "application.b", + version: "1.0.0", + path: applicationBPath, + dependencies: [] + }] + }); + return t.throws(projectPreprocessor.processTree(tree), + "Found at least two projects application.a and application.b of type application with the same distance to " + + "the root project. Only one project of type application can be used. Failed to decide which one to ignore.", + "Rejected with error"); +}); + +test("Multiple non-root application-projects on different levels", (t) => { + const tree = ({ + id: "library.a", + version: "1.0.0", + path: libraryAPath, + dependencies: [{ + id: "application.a", + version: "1.0.0", + path: applicationAPath, + dependencies: [] + }, { + id: "library.b", + version: "1.0.0", + path: libraryBPath, + dependencies: [{ + id: "application.b", + version: "1.0.0", + path: applicationBPath, + dependencies: [] + }] + }] + }); + return projectPreprocessor.processTree(tree).then((parsedTree) => { + t.deepEqual(parsedTree.id, "library.a", "Correct root project"); + t.deepEqual(parsedTree.dependencies.length, 2, "No dependency of the first level got ignored"); + t.deepEqual(parsedTree.dependencies[0].id, "application.a", "First application-project did not get ignored"); + t.deepEqual(parsedTree.dependencies[1].dependencies.length, 0, + "Second (deeper) application-project got ignored"); + }); +}); + +test("Root- and non-root application-projects", (t) => { + const tree = ({ + id: "application.a", + version: "1.0.0", + path: applicationAPath, + dependencies: [{ + id: "library.a", + version: "1.0.0", + path: libraryAPath, + dependencies: [{ + id: "application.b", + version: "1.0.0", + path: applicationBPath, + dependencies: [] + }] + }] + }); + return projectPreprocessor.processTree(tree).then((parsedTree) => { + t.deepEqual(parsedTree.id, "application.a", "Correct root project"); + t.deepEqual(parsedTree.dependencies[0].id, "library.a", "Correct library dependency"); + t.deepEqual(parsedTree.dependencies[0].dependencies[0], undefined, + "Second application-project dependency was ignored"); + }); +}); + +test("Ignores additional application-projects", (t) => { + const tree = ({ + id: "application.a", + version: "1.0.0", + path: applicationAPath, + dependencies: [{ + id: "application.b", + version: "1.0.0", + path: applicationBPath, + dependencies: [] + }] + }); + return projectPreprocessor.processTree(tree).then((parsedTree) => { + t.deepEqual(parsedTree, { + _level: 0, + type: "application", + metadata: { + name: "application.a" + }, + resources: { + configuration: { + paths: { + webapp: "webapp" + } + }, + pathMappings: { + "/": "webapp", + } + }, + dependencies: [], + id: "application.a", + kind: "project", + version: "1.0.0", + specVersion: "0.1", + path: applicationAPath + }, "Parsed correctly"); + }); +}); + test("Inconsistent dependencies with same ID", (t) => { // The one closer to the root should win const tree = {