Repository: SAP/open-ux-tools
Pull request #3179: Improve test coverage and add App Validation for `@sap-ux/repo-app-import-sub-generator`
Author: login: kjose90
Created: 2025-04-23T09:27:25Z | Merged: 2025-04-25T09:52:56Z

This PR includes the following updates and improvements to the `@sap-ux/repo-app-import-sub-generator` package:

- Enhanced test coverage
- Block sub-generator execution with validation error messages if the downloaded app does not contain a `qfa.json` file. This ensures only valid apps proceed through the import.
- Code cleanup


Diff:
diff --git a/.changeset/hot-boats-compare.md b/.changeset/hot-boats-compare.md
new file mode 100644
index 00000000000..469742e94ce
--- /dev/null
+++ b/.changeset/hot-boats-compare.md
@@ -0,0 +1,5 @@
+---
+'@sap-ux/repo-app-import-sub-generator': patch
+---
+
+Improve test coverage and add App Validation for @sap-ux/repo-app-import-sub-generator
diff --git a/packages/repo-app-import-sub-generator/src/app/index.ts b/packages/repo-app-import-sub-generator/src/app/index.ts
index 2886527f4ae..dc33cc7fe54 100644
--- a/packages/repo-app-import-sub-generator/src/app/index.ts
+++ b/packages/repo-app-import-sub-generator/src/app/index.ts
@@ -112,7 +112,11 @@ export default class extends Generator {
      */
     public async prompting(): Promise<void> {
         const quickDeployedAppConfig = this.options?.data?.quickDeployedAppConfig;
-        const questions: RepoAppDownloadQuestions[] = await getPrompts(this.appRootPath, quickDeployedAppConfig);
+        const questions: RepoAppDownloadQuestions[] = await getPrompts(
+            this.appRootPath,
+            quickDeployedAppConfig,
+            this.appWizard
+        );
         const answers: RepoAppDownloadAnswers = await this.prompt(questions);
         const { targetFolder } = answers;
         if (quickDeployedAppConfig?.appId) {
@@ -135,55 +139,44 @@ export default class extends Generator {
      * Writes the configuration files for the project, including deployment config, and README.
      */
     public async writing(): Promise<void> {
-        // Extract downloaded app
-        const archive = PromptState.downloadedAppPackage;
-        await extractZip(this.extractedProjectPath, archive, this.fs);
-
+        await extractZip(this.extractedProjectPath, this.fs);
         // Check if the qfa.json file
         const qfaJsonFilePath = join(this.extractedProjectPath, qfaJsonFileName);
-        if (this.fs.exists(qfaJsonFilePath)) {
-            const qfaJson: QfaJsonConfig = makeValidJson(qfaJsonFilePath, this.fs);
-            // Generate project files
-            validateQfaJsonFile(qfaJson);
-
-            // Generate app config
-            const config = await getAppConfig(this.answers.selectedApp, this.extractedProjectPath, qfaJson, this.fs);
-            await generate(this.projectPath, config, this.fs);
+        const qfaJson: QfaJsonConfig = makeValidJson(qfaJsonFilePath, this.fs);
+        // Generate project files
+        validateQfaJsonFile(qfaJson);
 
-            // Generate deploy config
-            const deployConfig: AbapDeployConfig = getAbapDeployConfig(this.answers.selectedApp, qfaJson);
-            await generateDeployConfig(this.projectPath, deployConfig, undefined, this.fs);
+        // Generate app config
+        const config = await getAppConfig(this.answers.selectedApp, this.extractedProjectPath, qfaJson, this.fs);
+        await generate(this.projectPath, config, this.fs);
 
-            if (this.vscode) {
-                // Generate Fiori launch config
-                const fioriOptions = this._getLaunchConfig(config);
-                // Create launch configuration
-                await createLaunchConfig(
-                    this.projectPath,
-                    fioriOptions,
-                    this.fs,
-                    RepoAppDownloadLogger.logger as unknown as Logger
-                );
-                writeApplicationInfoSettings(this.projectPath);
-            }
+        // Generate deploy config
+        const deployConfig: AbapDeployConfig = getAbapDeployConfig(this.answers.selectedApp, qfaJson);
+        await generateDeployConfig(this.projectPath, deployConfig, undefined, this.fs);
 
-            // Generate README
-            const readMeConfig = this._getReadMeConfig(config);
-            generateReadMe(this.projectPath, readMeConfig, this.fs);
+        if (this.vscode) {
+            // Generate Fiori launch config
+            const fioriOptions = this._getLaunchConfig(config);
+            // Create launch configuration
+            await createLaunchConfig(
+                this.projectPath,
+                fioriOptions,
+                this.fs,
+                RepoAppDownloadLogger.logger as unknown as Logger
+            );
+            writeApplicationInfoSettings(this.projectPath);
+        }
 
-            // Replace webapp files with downloaded app files
-            await replaceWebappFiles(this.projectPath, this.extractedProjectPath, this.fs);
+        // Generate README
+        const readMeConfig = this._getReadMeConfig(config);
+        generateReadMe(this.projectPath, readMeConfig, this.fs);
 
-            await validateAndUpdateManifestUI5Version(
-                join(this.projectPath, DirName.Webapp, FileName.Manifest),
-                this.fs
-            );
+        // Replace webapp files with downloaded app files
+        await replaceWebappFiles(this.projectPath, this.extractedProjectPath, this.fs);
 
-            // Clean up extracted project files
-            this.fs.delete(this.extractedProjectPath);
-        } else {
-            RepoAppDownloadLogger.logger?.error(t('error.qfaJsonNotFound', { jsonFileName: qfaJsonFileName }));
-        }
+        await validateAndUpdateManifestUI5Version(join(this.projectPath, DirName.Webapp, FileName.Manifest), this.fs);
+        // Clean up extracted project files
+        this.fs.delete(this.extractedProjectPath);
     }
 
     /**
@@ -322,6 +315,7 @@ export default class extends Generator {
     async end(): Promise<void> {
         try {
             this.appWizard.showInformation(t('info.repoAppDownloadCompleteMsg'), MessageType.notification);
+            await this._handlePostAppGeneration();
             await sendTelemetry(
                 EventName.GENERATION_SUCCESS,
                 TelemetryHelper.createTelemetryData({
@@ -331,7 +325,6 @@ export default class extends Generator {
             ).catch((error) => {
                 RepoAppDownloadLogger.logger?.error(t('error.telemetry', { error: error.message }));
             });
-            await this._handlePostAppGeneration();
         } catch (error) {
             RepoAppDownloadLogger.logger?.error(t('error.endPhase', { error: error.message }));
         }
diff --git a/packages/repo-app-import-sub-generator/src/prompts/prompt-state.ts b/packages/repo-app-import-sub-generator/src/prompts/prompt-state.ts
index 575d359d9f3..944f7a909d1 100644
--- a/packages/repo-app-import-sub-generator/src/prompts/prompt-state.ts
+++ b/packages/repo-app-import-sub-generator/src/prompts/prompt-state.ts
@@ -1,4 +1,5 @@
 import type { SystemSelectionAnswers } from '../app/types';
+import AdmZip from 'adm-zip';
 
 /**
  * Much of the values returned by the app downloader prompting are derived from prompt answers and are not direct answer values.
@@ -8,7 +9,7 @@ import type { SystemSelectionAnswers } from '../app/types';
  */
 export class PromptState {
     private static _systemSelection: SystemSelectionAnswers = {};
-    private static _downloadedAppPackage?: Buffer;
+    private static _admZipInstance?: AdmZip;
 
     /**
      * Returns the current state of the service config.
@@ -31,17 +32,17 @@ export class PromptState {
     /**
      * Set the downloaded app package.
      */
-    public static set downloadedAppPackage(archive: Buffer) {
-        this._downloadedAppPackage = archive;
+    public static set admZip(archive: Buffer) {
+        this._admZipInstance = new AdmZip(archive);
     }
 
     /**
-     * Returns the downloaded app package.
+     * Returns the AdmZip instance created from the downloaded archive.
      *
-     * @returns {Buffer} downloaded app package
+     * @returns {admZip | undefined} admZip instance
      */
-    public static get downloadedAppPackage(): Buffer {
-        return this._downloadedAppPackage ?? Buffer.alloc(0);
+    public static get admZip(): AdmZip | undefined {
+        return this._admZipInstance;
     }
 
     /**
@@ -64,6 +65,6 @@ export class PromptState {
 
     static reset(): void {
         PromptState.systemSelection = {};
-        PromptState._downloadedAppPackage = undefined;
+        PromptState._admZipInstance = undefined;
     }
 }
diff --git a/packages/repo-app-import-sub-generator/src/prompts/prompts.ts b/packages/repo-app-import-sub-generator/src/prompts/prompts.ts
index c148d6e5820..d5c42125a24 100644
--- a/packages/repo-app-import-sub-generator/src/prompts/prompts.ts
+++ b/packages/repo-app-import-sub-generator/src/prompts/prompts.ts
@@ -8,6 +8,7 @@ import { PromptState } from './prompt-state';
 import { fetchAppListForSelectedSystem, formatAppChoices } from './prompt-helpers';
 import type { FileBrowserQuestion } from '@sap-ux/inquirer-common';
 import { validateAppSelection } from '../utils/validators';
+import type { AppWizard } from '@sap-devx/yeoman-ui-types';
 
 /**
  * Gets the target folder selection prompt.
@@ -50,11 +51,13 @@ const getTargetFolderPrompt = (appRootPath?: string, appId?: string): FileBrowse
  *
  * @param {string} [appRootPath] - The root path of the application.
  * @param {QuickDeployedAppConfig} [quickDeployedAppConfig] - The quick deployed app configuration.
+ * @param {AppWizard} [appWizard] - The app wizard instance.
  * @returns {Promise<RepoAppDownloadQuestions[]>} A list of prompts for user interaction.
  */
 export async function getPrompts(
     appRootPath?: string,
-    quickDeployedAppConfig?: QuickDeployedAppConfig
+    quickDeployedAppConfig?: QuickDeployedAppConfig,
+    appWizard?: AppWizard
 ): Promise<RepoAppDownloadQuestions[]> {
     try {
         PromptState.reset();
@@ -90,7 +93,8 @@ export async function getPrompts(
                 },
                 message: t('prompts.appSelection.message'),
                 choices: (): { name: string; value: AppInfo }[] => (appList.length ? formatAppChoices(appList) : []),
-                validate: async (answers: AppInfo) => validateAppSelection(answers, appList, quickDeployedAppConfig)
+                validate: async (answers: AppInfo) =>
+                    validateAppSelection(answers, appList, quickDeployedAppConfig, appWizard)
             }
         ];
 
diff --git a/packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json b/packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json
index e3350373ade..d399afa53c0 100644
--- a/packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json
+++ b/packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json
@@ -1,7 +1,7 @@
 {
     "error": {
         "telemetry": "Failed to send telemetry data after downloading the application from the SAPUI5 ABAP Repository. {{- error}}",
-        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Check the file exists.",
+        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Ensure the downloaded application is properly deployed to the ABAP repository.",
         "replaceWebappFilesError": "An error occurred when replacing files in the downloaded application: {{- error}}. For more information, view the logs.",
         "requiredFieldsMissing": "Required fields are missing for the {{- app }} application. Check if the application is deployed correctly.",
         "applicationListFetchError": "An error occurred when fetching the application list: {{- error}}. Check if your application is available in the system.",
diff --git a/packages/repo-app-import-sub-generator/src/utils/download-utils.ts b/packages/repo-app-import-sub-generator/src/utils/download-utils.ts
index f7790c952fa..6877a505bfa 100644
--- a/packages/repo-app-import-sub-generator/src/utils/download-utils.ts
+++ b/packages/repo-app-import-sub-generator/src/utils/download-utils.ts
@@ -1,22 +1,31 @@
 import type { AbapServiceProvider } from '@sap-ux/axios-extension';
-import AdmZip from 'adm-zip';
 import { join } from 'path';
 import type { Editor } from 'mem-fs-editor';
 import { PromptState } from '../prompts/prompt-state';
 import { t } from './i18n';
 import RepoAppDownloadLogger from '../utils/logger';
+import { qfaJsonFileName } from './constants';
+
+/**
+ * Checks whether the ZIP archive contains an entry named qfa.json
+ * and verifies that a file named qfa.json exists  in the archive.
+ *
+ * @returns {boolean} true qfa.json file exists, otherwise false.
+ */
+export function hasQfaJson(): boolean {
+    const qfaEntries = PromptState.admZip?.getEntries().filter((entry) => entry.entryName === qfaJsonFileName);
+    return qfaEntries?.length === 1;
+}
 
 /**
  * Extracts a ZIP archive to a temporary directory.
  *
  * @param {string} extractedProjectPath - The path where the archive should be extracted.
- * @param {Buffer} archive - The ZIP archive buffer.
  * @param {Editor} fs - The file system editor.
  */
-export async function extractZip(extractedProjectPath: string, archive: Buffer, fs: Editor): Promise<void> {
+export async function extractZip(extractedProjectPath: string, fs: Editor): Promise<void> {
     try {
-        const zip = new AdmZip(archive);
-        zip.getEntries().forEach(function (zipEntry) {
+        PromptState.admZip?.getEntries().forEach(function (zipEntry) {
             if (!zipEntry.isDirectory) {
                 // Extract the file content
                 const fileContent = zipEntry.getData().toString('utf8');
@@ -38,5 +47,5 @@ export async function downloadApp(repoName: string): Promise<void> {
     const serviceProvider = PromptState.systemSelection?.connectedSystem?.serviceProvider as AbapServiceProvider;
     const downloadedAppPackage = await serviceProvider.getUi5AbapRepository().downloadFiles(repoName);
     // store downloaded package in prompt state
-    PromptState.downloadedAppPackage = downloadedAppPackage;
+    PromptState.admZip = downloadedAppPackage;
 }
diff --git a/packages/repo-app-import-sub-generator/src/utils/validators.ts b/packages/repo-app-import-sub-generator/src/utils/validators.ts
index b510b7d6d83..b60eab25e70 100644
--- a/packages/repo-app-import-sub-generator/src/utils/validators.ts
+++ b/packages/repo-app-import-sub-generator/src/utils/validators.ts
@@ -6,7 +6,10 @@ import { HELP_NODES } from '@sap-ux/guided-answers-helper';
 import type { ValidationLink } from '@sap-ux/inquirer-common';
 import { ERROR_TYPE, ErrorHandler } from '@sap-ux/inquirer-common';
 import type { AppInfo, QuickDeployedAppConfig, QfaJsonConfig } from '../app/types';
-import { downloadApp } from '../utils/download-utils';
+import { downloadApp, hasQfaJson } from '../utils/download-utils';
+import type { AppWizard } from '@sap-devx/yeoman-ui-types';
+import { MessageType } from '@sap-devx/yeoman-ui-types';
+import { qfaJsonFileName } from '../utils/constants';
 
 /**
  * Validates the metadata section of the app configuration.
@@ -117,12 +120,14 @@ async function generateAppNotFoundHelpLink(): Promise<ValidationLink> {
  * @param answers - The selected app information.
  * @param appList - The list of available apps.
  * @param quickDeployedAppConfig - The quick deployed app configuration.
+ * @param appWizard - The app wizard instance.
  * @returns A promise resolving to a boolean or a validation error message.
  */
 export async function validateAppSelection(
     answers: AppInfo,
     appList: AppIndex,
-    quickDeployedAppConfig?: QuickDeployedAppConfig
+    quickDeployedAppConfig?: QuickDeployedAppConfig,
+    appWizard?: AppWizard
 ): Promise<string | boolean | ValidationLink> {
     // Quick deploy config exists but no apps found
     if (quickDeployedAppConfig?.appId && appList.length === 0) {
@@ -138,7 +143,14 @@ export async function validateAppSelection(
     if (answers?.appId) {
         try {
             await downloadApp(answers.repoName);
-            return true;
+            const isQfaJsonPresent: boolean = hasQfaJson();
+            if (!isQfaJsonPresent) {
+                appWizard?.showError(
+                    t('error.qfaJsonNotFound', { jsonFileName: qfaJsonFileName }),
+                    MessageType.notification
+                );
+            }
+            return isQfaJsonPresent;
         } catch (error) {
             return t('error.appDownloadErrors.appDownloadFailure', { error: error.message });
         }
diff --git a/packages/repo-app-import-sub-generator/test/app.test.ts b/packages/repo-app-import-sub-generator/test/app.test.ts
index 875308f2976..b54816be09e 100644
--- a/packages/repo-app-import-sub-generator/test/app.test.ts
+++ b/packages/repo-app-import-sub-generator/test/app.test.ts
@@ -8,15 +8,18 @@ import fs from 'fs';
 import { TestFixture } from './fixtures';
 import { getAppConfig } from '../src/app/app-config';
 import { OdataVersion } from '@sap-ux/odata-service-inquirer';
-import { TemplateType, type FioriElementsApp, type LROPSettings } from '@sap-ux/fiori-elements-writer';
-import { adtSourceTemplateId, fioriAppSourcetemplateId, extractedFilePath } from '../src/utils/constants';
+import { TemplateType, type FioriElementsApp, type LROPSettings, generate } from '@sap-ux/fiori-elements-writer';
+import { adtSourceTemplateId, fioriAppSourcetemplateId, extractedFilePath, qfaJsonFileName } from '../src/utils/constants';
 import { removeSync } from 'fs-extra';
 import { isValidPromptState } from '../src/utils/validators';
-import { hostEnvironment, sendTelemetry } from '@sap-ux/fiori-generator-shared';
+import { hostEnvironment, sendTelemetry, TelemetryHelper, generateReadMe } from '@sap-ux/fiori-generator-shared';
 import { FileName, DirName, type Manifest } from '@sap-ux/project-access';
 import RepoAppDownloadLogger from '../src/utils/logger';
 import { t } from '../src/utils/i18n';
 import env from 'yeoman-environment';
+import { createLaunchConfig, updateWorkspaceFoldersIfNeeded, handleWorkspaceConfig } from '@sap-ux/launch-config';
+import { extractZip, hasQfaJson } from '../src/utils/download-utils';
+import { EventName } from '../src/telemetryEvents';
 
 jest.mock('../src/prompts/prompt-helpers', () => ({
 	...jest.requireActual('../src/prompts/prompt-helpers'),
@@ -24,7 +27,6 @@ jest.mock('../src/prompts/prompt-helpers', () => ({
 }));
 
 jest.mock('../src/utils/logger', () => ({
-	...jest.requireActual('../src/utils/logger'),
     logger: {
         error: jest.fn(), 
 		info: jest.fn(),
@@ -33,7 +35,18 @@ jest.mock('../src/utils/logger', () => ({
 	configureLogging: jest.fn()
 }));
 
-jest.mock('../src/utils/download-utils');
+jest.mock('@sap-ux/launch-config', () => ({
+	...jest.requireActual('@sap-ux/launch-config'),
+	createLaunchConfig: jest.fn(),
+	updateWorkspaceFoldersIfNeeded: jest.fn(),
+	handleWorkspaceConfig: jest.fn()
+}));
+
+jest.mock('../src/utils/download-utils', () => ({
+	extractZip: jest.fn(),
+	hasQfaJson: jest.fn()
+}));
+
 jest.mock('../src/app/app-config', () => ({
     ...jest.requireActual('../src/app/app-config'),
 	getAppConfig: jest.fn()
@@ -129,7 +142,8 @@ function mockPrompts(testOutputDir: string): void {
 
 function copyFilesToExtractedProjectPath(
     testFixtureDir: string,
-    extractedProjectPath: string
+    extractedProjectPath: string,
+	skipQFAJsonCopyAndTriggerError: boolean = false
 ): void {
     if (!fs.existsSync(extractedProjectPath)) {
         fs.mkdirSync(extractedProjectPath, { recursive: true });
@@ -140,6 +154,10 @@ function copyFilesToExtractedProjectPath(
     files.forEach((file) => {
 		const sourceFilePath = join(testFixtureDir, file);
 		const destinationFilePath = join(extractedProjectPath, file);
+        if (file === qfaJsonFileName && skipQFAJsonCopyAndTriggerError) {
+			// Skip copying qfa.json and trigger error
+            return;
+        }
 		if(file === 'i18n') {
 			// Create the directory if it doesn't exist
 			if (!fs.existsSync(destinationFilePath)) {
@@ -154,7 +172,6 @@ function copyFilesToExtractedProjectPath(
     });
 }
 
-
 function verifyGeneratedFiles(testOutputDir: string, appId: string, testFixtureDir: string): void {
     const projectPath = join(`${testOutputDir}/${appId}`);
     expect(fs.existsSync(projectPath)).toBe(true);
@@ -215,7 +232,6 @@ describe('Repo App Download', () => {
 	const appId = 'test-app-id', repoName = 'app-1-repo';
 	const extractedProjectPath = join(testOutputDir, appId, extractedFilePath);
 	const testFixtureDir = join(__dirname, 'fixtures', 'downloaded-app');
-	copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath);
 	
     afterEach(() => {
 		jest.clearAllMocks();
@@ -225,7 +241,6 @@ describe('Repo App Download', () => {
 	})
 
 	beforeEach(() => {
-		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath);
 		appConfig = createAppConfig(appId, metadata);
         mockPrompts(testOutputDir);
 		mockVSCode = {
@@ -243,8 +258,15 @@ describe('Repo App Download', () => {
 	});
 
     it('Should successfully run app download from repository', async () => {
+		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath);
 		(isValidPromptState as jest.Mock).mockReturnValue(true);
 		(getAppConfig as jest.Mock).mockResolvedValue(appConfig);
+		(handleWorkspaceConfig as jest.Mock).mockReturnValue({
+			launchJsonPath: join(testOutputDir, '.vscode', 'launch.json'),
+			cwd: testOutputDir,
+			workspaceFolderUri: 'testUri',
+			appNotInWorkspace: true
+		});
 		await expect( 
 			yeomanTest
 				.run(RepoAppDownloadGenerator, { 
@@ -272,10 +294,57 @@ describe('Repo App Download', () => {
 		.resolves.not.toThrow();
 		verifyGeneratedFiles(testOutputDir, appId, testFixtureDir);
 		expect(mockAppWizard.showInformation).toHaveBeenCalledWith(t('info.repoAppDownloadCompleteMsg'), MessageType.notification);
+		expect(mockSendTelemetry).toHaveBeenCalledWith(
+			EventName.GENERATION_SUCCESS,
+			TelemetryHelper.createTelemetryData({
+				appType: 'repo-app-import-sub-generator'
+			}
+		));
+		expect(RepoAppDownloadLogger.logger.info).toHaveBeenCalledWith(t('info.installationErrors.skippedInstallation'));
+    });
+
+	it('Should successfully run app download from repository when Quick Deploy App Config is provided', async () => {
+		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath);
+		(isValidPromptState as jest.Mock).mockReturnValue(true);
+		(getAppConfig as jest.Mock).mockResolvedValue(appConfig);
+		await expect( 
+			yeomanTest
+				.run(RepoAppDownloadGenerator, { 
+					resolved: repoAppDownloadGenPath
+				})
+				.cd('.')
+				.withOptions({ 
+					appRootPath: testOutputDir, 
+					appWizard: mockAppWizard,
+					vscode: mockVSCode, 
+					skipInstall: true,
+					data: {
+						quickDeployedAppConfig: {
+							appId: appConfig.app.id,
+							serviceProviderInfo: { name: 'system3' }
+						}
+					}
+				})
+				.withPrompts({
+					systemSelection: 'system3',
+					selectedApp: {
+						appId: appConfig.app.id,
+						title: appConfig.app.title,
+						description: appConfig.app.description,
+						repoName: 'app-1-repo',
+						url: 'url-1'
+					},
+					targetFolder: testOutputDir
+				})
+			)
+		.resolves.not.toThrow();
+		verifyGeneratedFiles(testOutputDir, appId, testFixtureDir);
+		expect(mockAppWizard.showInformation).toHaveBeenCalledWith(t('info.repoAppDownloadCompleteMsg'), MessageType.notification);
 		expect(RepoAppDownloadLogger.logger.info).toHaveBeenCalledWith(t('info.installationErrors.skippedInstallation'));
     });
 
 	it('Should not throw error in end phase if telemetry fails', async () => {
+		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath);
 		const errorMsg = 'Telemetry error';
      	mockSendTelemetry.mockRejectedValue(new Error(errorMsg));
 		(isValidPromptState as jest.Mock).mockReturnValue(true);
@@ -310,8 +379,15 @@ describe('Repo App Download', () => {
     });
 
 	it('Should execute post app gen hook event when postGenCommand is provided', async () => {
+		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath);
 		(isValidPromptState as jest.Mock).mockReturnValue(true);
 		(getAppConfig as jest.Mock).mockResolvedValue(appConfig);
+		(handleWorkspaceConfig as jest.Mock).mockResolvedValue({
+			launchJsonPath: join(testOutputDir, '.vscode', 'launch.json'),
+			cwd: testOutputDir,
+			workspaceFolderUri: undefined,
+			appNotInWorkspace: false
+		});
         
         await expect( 
 			yeomanTest
@@ -321,7 +397,6 @@ describe('Repo App Download', () => {
 				.cd('.')
 				.withOptions({ 
 					appRootPath: testOutputDir, 
-					appWizard: mockAppWizard,
 					vscode: mockVSCode, 
 					data: {
 						postGenCommand: 'test-post-gen-command'
@@ -340,10 +415,18 @@ describe('Repo App Download', () => {
 				})
 		)
 		.resolves.not.toThrow();
+		expect(mockSendTelemetry).toHaveBeenCalledWith(
+			EventName.GENERATION_SUCCESS,
+			TelemetryHelper.createTelemetryData({
+				appType: 'repo-app-import-sub-generator'
+			}
+		));
+		expect(handleWorkspaceConfig).toHaveBeenCalled();
 		verifyGeneratedFiles(testOutputDir, appId, testFixtureDir);
     });
 
 	it('should successfully download a quick deployed app from repository', async () => {
+		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath);
 		const yeomanEnv = env.createEnv();
 	
 		const generator = new RepoAppDownloadGenerator([], {
@@ -365,5 +448,113 @@ describe('Repo App Download', () => {
 	
 		expect((generator as any)._runNpmInstall).toHaveBeenCalled();
 	});
+
+	it('should successfully download a quick deployed app from repository', async () => {
+		const yeomanEnv = env.createEnv();
+
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		// mock installation
+		(generator as any).spawnCommand = jest.fn().mockResolvedValue(undefined);
+		await generator.install();
+		expect((generator as any).spawnCommand).toHaveBeenCalled();
+	});
+
+	it('should log an error if installation of a quick deployed app from repository', async () => {
+		const yeomanEnv = env.createEnv();
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		// mock installation
+		(generator as any).spawnCommand = jest.fn().mockRejectedValue(
+			new Error('Installation error')
+		);
+		await generator.install();
+		expect(RepoAppDownloadLogger.logger.error).toHaveBeenCalledWith(
+			t('error.installationErrors.npmInstall', { error: 'Error: Installation error' })
+		);
+	});
+
+	it('should set conflicter.force to true and initialize telemetry', async () => {
+		const yeomanEnv = env.createEnv();
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		// Mock Yeoman environment with a conflicter object
+		(generator.env as any) = {
+			conflicter: { force: false }
+		};
+
+		await generator.initializing();
+
+		expect((generator.env as any).conflicter.force).toBe(true);
+		expect(TelemetryHelper.initTelemetrySettings).toHaveBeenCalledWith(
+			expect.objectContaining({
+				consumerModule: expect.objectContaining({
+					name: expect.any(String),
+					version: expect.any(String)
+				}),
+				internalFeature: expect.any(Boolean),
+				watchTelemetrySettingStore: false
+			})
+		);
+	});
+
+	it('should set callback function on prompts if prompts exist', () => {
+		const yeomanEnv = env.createEnv();
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		Object.defineProperty(generator, 'prompts', {
+			value: {
+				setCallback: jest.fn()
+			},
+			writable: false
+		});
+		const mockFn = jest.fn();
+		generator.setPromptsCallback(mockFn);
+
+		expect(generator['prompts'].setCallback).toHaveBeenCalledWith(mockFn);
+	});
 });
-  
\ No newline at end of file
diff --git a/packages/repo-app-import-sub-generator/test/prompts/prompt-state.test.ts b/packages/repo-app-import-sub-generator/test/prompts/prompt-state.test.ts
index 999beb93226..774310a8209 100644
--- a/packages/repo-app-import-sub-generator/test/prompts/prompt-state.test.ts
+++ b/packages/repo-app-import-sub-generator/test/prompts/prompt-state.test.ts
@@ -1,7 +1,9 @@
 import { PromptState } from '../../src/prompts/prompt-state';
 import type { SystemSelectionAnswers } from '../../src/app/types';
 import type { AbapServiceProvider } from '@sap-ux/axios-extension';
+import AdmZip from 'adm-zip';
 
+jest.mock('adm-zip');
 describe('PromptState', () => {
     const mockServiceProvider = {
         defaults: {
@@ -37,14 +39,18 @@ describe('PromptState', () => {
         expect(PromptState.systemSelection).toEqual({});
     });
 
-    it('should set and get downloadedAppPackage correctly', () => {
-        const mockBuffer = Buffer.from('mock zip content');
-        PromptState.downloadedAppPackage = mockBuffer;
-        expect(PromptState.downloadedAppPackage).toBe(mockBuffer);
+    it('should set and get the AdmZip instance correctly', () => {
+        const mockBuffer = Buffer.from('mock-zip-content');
+
+        PromptState.admZip = mockBuffer;
+        const admZipInstance = PromptState.admZip;
+        expect(admZipInstance).toBeDefined();
+        expect(admZipInstance).toBeInstanceOf(AdmZip);
     });
 
-    it('should return undefined for downloadedAppPackage if not set', () => {
-        expect(PromptState.downloadedAppPackage.length).toBe(0);
+    it('should return undefined if admZip is not set', () => {
+        const admZipInstance = PromptState.admZip;
+        expect(admZipInstance).toBeUndefined();
     });
 
     it('should return baseURL from connected system', () => {
diff --git a/packages/repo-app-import-sub-generator/test/prompts/prompts.test.ts b/packages/repo-app-import-sub-generator/test/prompts/prompts.test.ts
index b55025f5fd7..5e3667146c5 100644
--- a/packages/repo-app-import-sub-generator/test/prompts/prompts.test.ts
+++ b/packages/repo-app-import-sub-generator/test/prompts/prompts.test.ts
@@ -1,35 +1,24 @@
 import { t } from '../../src/utils/i18n';
 import { validateFioriAppTargetFolder } from '@sap-ux/project-input-validator';
 import { getPrompts } from '../../src/prompts/prompts';
-import { PromptNames } from '../../src/app/types';
+import { PromptNames, type QuickDeployedAppConfig } from '../../src/app/types';
 import { PromptState } from '../../src/prompts/prompt-state';
 import * as helpers from '../../src/prompts/prompt-helpers';
 import * as downloadUtils from '../../src/utils/download-utils';
 import RepoAppDownloadLogger from '../../src/utils/logger';
 import type { AbapServiceProvider } from '@sap-ux/axios-extension';
+import { formatAppChoices } from '../../src/prompts/prompt-helpers';
+import { format } from 'path';
+import { validateAppSelection } from '../../src/utils/validators';
+import app from '../../src/app';
 
 jest.mock('@sap-ux/odata-service-inquirer', () => ({
-    getSystemSelectionQuestions: jest.fn().mockResolvedValue({
-        prompts: [{
-            name: 'systemSelection',
-            type: 'list',
-            choices: [{ name: 'Sys', value: { system: { name: 'Sys' } } }]
-        }],
-        answers: {
-            connectedSystem: { serviceProvider: {} }
-        }
-    })
+    getSystemSelectionQuestions: jest.fn()
 }));
 
 jest.mock('../../src/prompts/prompt-helpers', () => ({
-    fetchAppListForSelectedSystem: jest.fn().mockResolvedValue([
-        { appId: 'app1', repoName: 'repo1' },
-        { appId: 'app2', repoName: 'repo2' }
-    ]),
-    formatAppChoices: jest.fn().mockReturnValue([
-        { name: 'App 1', value: { appId: 'app1', repoName: 'repo1' } },
-        { name: 'App 2', value: { appId: 'app2', repoName: 'repo2' } }
-    ])
+    fetchAppListForSelectedSystem: jest.fn(),
+    formatAppChoices: jest.fn()
 }));
 
 jest.mock('../../src/utils/download-utils', () => ({
@@ -40,8 +29,8 @@ jest.mock('@sap-ux/project-input-validator', () => ({
     validateFioriAppTargetFolder: jest.fn().mockResolvedValue(true)
 }));
 
-jest.mock('@sap-ux/project-input-validator', () => ({
-    validateFioriAppTargetFolder: jest.fn().mockResolvedValue(true)
+jest.mock('../../src/utils/validators', () => ({
+    validateAppSelection: jest.fn()
 }));
 
 describe('getPrompts', () => {
@@ -49,13 +38,29 @@ describe('getPrompts', () => {
     const mockFetchAppList = helpers.fetchAppListForSelectedSystem as jest.Mock;
     const mockDownloadApp = downloadUtils.downloadApp as jest.Mock;
 
+    const mockServiceProvider = {
+        defaults: {
+            baseURL: 'https://mock.sap-system.com',
+            params: {
+                'sap-client': '100'
+            }
+        },
+        name: 'System 1'
+    } as unknown as AbapServiceProvider;
+
+    const appRootPath = '/app/path';
+    const appValue = { appId: 'app1', repoName: 'repo1' } 
+    const appList = [
+        { name: 'App 1', value: appValue }
+    ]
+
     beforeEach(() => {
         jest.clearAllMocks();
         jest.resetAllMocks();
         PromptState.reset();
     });
 
-    it('should return prompts including system, app, and target folder', async () => {
+    it('should return system, app, and target folder prompts without Quick Deployed App config', async () => {
         mockGetSystemSelectionQuestions.mockResolvedValue({
             prompts: [{
                 name: PromptNames.systemSelection,
@@ -63,171 +68,183 @@ describe('getPrompts', () => {
                 choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }]
             }],
             answers: {
-                connectedSystem: { serviceProvider: {} }
+                connectedSystem: { serviceProvider: mockServiceProvider }
             }
         });
 
+        (formatAppChoices as jest.Mock).mockReturnValue(appList);
+
+        (validateAppSelection as jest.Mock).mockResolvedValue(true);
+        (validateFioriAppTargetFolder as jest.Mock).mockResolvedValue(true);
+
         mockFetchAppList.mockResolvedValue([{ appId: 'app1', repoName: 'repo1' }]);
         mockDownloadApp.mockResolvedValue(undefined);
 
-        const prompts = await getPrompts('/app/path');
-        expect(prompts).toBeInstanceOf(Array);
-        expect(prompts.find(p => p.name === PromptNames.systemSelection)).toBeTruthy();
-        expect(prompts.find(p => p.name === PromptNames.selectedApp)).toBeTruthy();
-        expect(prompts.find(p => p.name === PromptNames.targetFolder)).toBeTruthy();
-    });
-
-    it('should preselect default system if quickDeployedAppConfig is provided', async () => {
-        const quickDeployedAppConfig = {
-            appId: 'app1',
-            serviceProviderInfo: {
-                name: 'DefaultSystem'
-            }
-        };
+        const prompts = await getPrompts(appRootPath);
+        
+        // system selection prompt
+        const systemSelectionPrompt = prompts.find(p => p.name === PromptNames.systemSelection);
+       
+        expect(systemSelectionPrompt).toBeDefined();
+        expect(systemSelectionPrompt).toHaveProperty('type', 'list');
+        expect(systemSelectionPrompt).toHaveProperty('choices', [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }]);
+        expect(systemSelectionPrompt).toHaveProperty('name', PromptNames.systemSelection);
 
-        mockGetSystemSelectionQuestions.mockResolvedValue({
-            prompts: [{
-                name: PromptNames.systemSelection,
-                type: 'list',
-                choices: [
-                    { name: 'System A', value: { system: { name: 'SystemA' } } },
-                    { name: 'Default System', value: { system: { name: 'DefaultSystem' } } }
-                ],
-                default: 'DefaultSystem'
-            }],
-            answers: {
-                connectedSystem: { serviceProvider: {} }
-            }
+        // app selection prompt
+        const appSelectionPrompt = prompts.find(p => p.name === PromptNames.selectedApp);
+        expect(appSelectionPrompt).toBeDefined();
+        expect(appSelectionPrompt).toHaveProperty('type', 'list');
+        expect(await (appSelectionPrompt as any)?.when({ [PromptNames.systemSelection]: mockServiceProvider })).toBe(true);
+        expect((appSelectionPrompt as any)?.choices()).toEqual(appList);
+        expect(appSelectionPrompt).toHaveProperty('when');
+        expect(appSelectionPrompt).toHaveProperty('message', t('prompts.appSelection.message'));
+        expect(appSelectionPrompt).toHaveProperty('guiOptions', {
+            mandatory: false,
+            breadcrumb: t('prompts.appSelection.breadcrumb')
         });
+        expect(await (appSelectionPrompt as any)?.validate(appValue)).toBe(true);
+        expect((appSelectionPrompt as any)?.default()).toBe(undefined);
+        expect(appSelectionPrompt).toHaveProperty('name', PromptNames.selectedApp);
 
-        mockFetchAppList.mockResolvedValue([{ appId: 'app1', repoName: 'repo1' }]);
-        const prompts = await getPrompts(undefined, quickDeployedAppConfig);
-
-        const systemPrompt = prompts.find(p => p.name === PromptNames.systemSelection) as any;
-        expect(systemPrompt.default).toBe('DefaultSystem');
-    });
-    
-    it('should use validateFioriAppTargetFolder in folder prompt', async () => {
-        mockGetSystemSelectionQuestions.mockResolvedValue({
-            prompts: [],
-            answers: {}
-        });
-        const prompts = await getPrompts('/some/path');
-        const projectPathPrompt = prompts.find(p => p.name === PromptNames.targetFolder) as any;
-        await projectPathPrompt.validate('/some/path', {
-            selectedApp: { appId: 'id1' }
+        // target folder prompt
+        const targetFolderPrompt = prompts.find(p => p.name === PromptNames.targetFolder);
+        expect(targetFolderPrompt).toBeDefined();
+        expect(targetFolderPrompt).toHaveProperty('type', 'input');
+        expect(targetFolderPrompt).toHaveProperty('message', t('prompts.targetPath.message'));
+        expect(targetFolderPrompt).toHaveProperty('guiOptions', {
+            mandatory: true,
+            applyDefaultWhenDirty: true,
+            breadcrumb: t('prompts.targetPath.breadcrumb')
         });
-        expect(validateFioriAppTargetFolder).toHaveBeenCalledWith('/some/path', 'id1', true);
+        expect(await (targetFolderPrompt as any)?.validate(appRootPath, appValue)).toBe(true);
+        expect(targetFolderPrompt?.default()).toBe(appRootPath);
     });
 
-    it('should handle quickDeployedAppConfig and return the correct prompts', async () => {
+    it('should return system, app, and target folder prompts with Quick Deployed App config', async () => {
         mockGetSystemSelectionQuestions.mockResolvedValue({
             prompts: [{
                 name: PromptNames.systemSelection,
                 type: 'list',
-                choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }]
+                choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }],
+                default: 0
             }],
             answers: {
-                connectedSystem: { serviceProvider: {} }
+                connectedSystem: { serviceProvider: mockServiceProvider }
             }
         });
-        const quickDeployedAppConfig = {
-            appId: 'app1',
-            serviceProviderInfo: { name: 'System 1' }
-        };
-        // Call getPrompts with quickDeployedAppConfig
-        const prompts = await getPrompts('/some/path', quickDeployedAppConfig);
 
-        // Ensure prompts are returned correctly
-        expect(prompts).toBeDefined();
+        (formatAppChoices as jest.Mock).mockReturnValue(appList);
+
+        (validateAppSelection as jest.Mock).mockResolvedValue(true);
+        (validateFioriAppTargetFolder as jest.Mock).mockResolvedValue(true);
+
+        mockFetchAppList.mockResolvedValue([{ appId: 'app1', repoName: 'repo1' }]);
+        mockDownloadApp.mockResolvedValue(undefined);
 
-        // Check if the system selection prompt exists
+        const quickDeployedAppConfig = {
+            appId: 'app1',
+            serviceProviderInfo: mockServiceProvider
+        } as unknown as QuickDeployedAppConfig;
+        const prompts = await getPrompts(appRootPath, quickDeployedAppConfig);
+        
+        // system selection prompt
         const systemSelectionPrompt = prompts.find(p => p.name === PromptNames.systemSelection);
         expect(systemSelectionPrompt).toBeDefined();
+        expect(systemSelectionPrompt).toHaveProperty('type', 'list');
+        expect(systemSelectionPrompt).toHaveProperty('choices', [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }]);
+        expect((systemSelectionPrompt as any)?.default).toBe(0);
+        expect(systemSelectionPrompt).toHaveProperty('name', PromptNames.systemSelection);
 
-        // Check if the system selection prompt is filtered correctly
-        if (systemSelectionPrompt) {
-            const listPrompt = systemSelectionPrompt as unknown as { choices: () => { name: string; value: {} }[] };
-            expect(listPrompt.choices).toEqual([
-                { name: 'System 1', value: { system: { name: 'MockSystem' } } }
-            ]);
-        }
-
-        // Check if the app selection prompt exists and is populated correctly
+        // app selection prompt
         const appSelectionPrompt = prompts.find(p => p.name === PromptNames.selectedApp);
         expect(appSelectionPrompt).toBeDefined();
-        if (appSelectionPrompt) {
-            const listPrompt = appSelectionPrompt as unknown as { choices: () => { name: string; value: {} }[] };
-            expect(appSelectionPrompt.when).toBeTruthy();
-        }
+        expect(appSelectionPrompt).toHaveProperty('type', 'list');
+        expect(await (appSelectionPrompt as any)?.when({ [PromptNames.systemSelection]: mockServiceProvider })).toBe(true);
+        expect((appSelectionPrompt as any)?.choices()).toEqual(appList);
+        expect(appSelectionPrompt).toHaveProperty('when');
+        expect(appSelectionPrompt).toHaveProperty('message', t('prompts.appSelection.message'));
+        expect(appSelectionPrompt).toHaveProperty('guiOptions', {
+            mandatory: false,
+            breadcrumb: t('prompts.appSelection.breadcrumb')
+        });
+        expect(await (appSelectionPrompt as any)?.validate(appValue)).toBe(true);
+        expect((appSelectionPrompt as any)?.default()).toBe(0);
+        expect(appSelectionPrompt).toHaveProperty('name', PromptNames.selectedApp);
 
-        // Check if the target folder prompt exists and is included
+        // target folder prompt
         const targetFolderPrompt = prompts.find(p => p.name === PromptNames.targetFolder);
         expect(targetFolderPrompt).toBeDefined();
-        if (targetFolderPrompt) {
-            expect(targetFolderPrompt.when).toBeTruthy();
-        }
+        expect(targetFolderPrompt).toHaveProperty('type', 'input');
+        expect(targetFolderPrompt).toHaveProperty('message', t('prompts.targetPath.message'));
+        expect(targetFolderPrompt).toHaveProperty('guiOptions', {
+            mandatory: true,
+            applyDefaultWhenDirty: true,
+            breadcrumb: t('prompts.targetPath.breadcrumb')
+        });
+        expect(await (targetFolderPrompt as any)?.when({ [PromptNames.targetFolder]: appRootPath})).toBe(true);
+        expect(await (targetFolderPrompt as any)?.validate(appRootPath, appValue)).toBe(true);
     });
 
-    it('should display app selection prompt when system is valid', async () => {
-        const mockServiceProvider = {
-            name: 'MockSystem'
-        } as unknown as AbapServiceProvider;
+    it('should return prompts allowing the user to override the default path and app selection when Quick Deployed App config is provided', async () => {
         mockGetSystemSelectionQuestions.mockResolvedValue({
             prompts: [{
                 name: PromptNames.systemSelection,
                 type: 'list',
-                choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }]
+                choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }],
+                default: 0
             }],
             answers: {
                 connectedSystem: { serviceProvider: mockServiceProvider }
             }
         });
-    
-        const prompts = await getPrompts('/app/path');
-        expect(mockGetSystemSelectionQuestions).toHaveBeenCalledWith(
-            {
-                serviceSelection: { hide: true },
-                systemSelection: { defaultChoice: undefined }
-            },
-            true
-        );
-        const appSelectionPrompt = prompts.find(p => p.name === PromptNames.selectedApp);
-        (appSelectionPrompt?.when as Function)({
-            [PromptNames.systemSelection]: mockServiceProvider
-        })
-    });
 
-    it('should not app selection prompt when selected system is valid', async () => {
-        const mockServiceProvider = {
-            defaults: {
-                baseURL: 'https://mock.sap-system.com',
-                params: {
-                    'sap-client': '100'
-                }
-            }
-        } as unknown as AbapServiceProvider;
-        mockGetSystemSelectionQuestions.mockResolvedValue({
-            prompts: [{
-                name: PromptNames.systemSelection,
-                type: 'list',
-                choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }]
-            }],
-            answers: {
-                connectedSystem: { serviceProvider: null }
-            }
-        });
+        const appListWithMoreOptions = [
+            ...appList,
+            { name: 'App 2', value: { appId: 'app2', repoName: 'repo2' } },
+            { name: 'App 3', value: { appId: 'app3', repoName: 'repo3' }  }
+        ];
+
+        (formatAppChoices as jest.Mock).mockReturnValue(appListWithMoreOptions);
+
+        (validateAppSelection as jest.Mock).mockResolvedValue(true);
+        (validateFioriAppTargetFolder as jest.Mock).mockResolvedValue(true);
 
         mockFetchAppList.mockResolvedValue([{ appId: 'app1', repoName: 'repo1' }]);
         mockDownloadApp.mockResolvedValue(undefined);
 
-        const prompts = await getPrompts('/app/path');
+        const quickDeployedAppConfig = {
+            serviceProviderInfo: mockServiceProvider
+        } as unknown as QuickDeployedAppConfig;
+        const prompts = await getPrompts(appRootPath, quickDeployedAppConfig);
+        
+        // system selection prompt
+        const systemSelectionPrompt = prompts.find(p => p.name === PromptNames.systemSelection);
+        expect(systemSelectionPrompt).toBeDefined();
+
+        // app selection prompt
+        const selectedAnswer = { 
+            [PromptNames.systemSelection]: mockServiceProvider,
+            [PromptNames.selectedApp]: { appId: 'app3', repoName: 'repo3' },
+            [PromptNames.targetFolder]: 'someother/path'
+        };
         const appSelectionPrompt = prompts.find(p => p.name === PromptNames.selectedApp);
-        (appSelectionPrompt?.when as Function)({
-            [PromptNames.systemSelection]: mockServiceProvider
-        })
-        expect(mockFetchAppList as jest.Mock).not.toHaveBeenCalled();
+        expect(await (appSelectionPrompt as any)?.when(selectedAnswer)).toBe(true);
+        expect(await (appSelectionPrompt as any)?.validate(appValue)).toBe(true);
+        //expect((appSelectionPrompt as any)?.default()).toBe(0);
+        expect(appSelectionPrompt).toHaveProperty('name', PromptNames.selectedApp);
+
+        // target folder prompt
+        const targetFolderPrompt = prompts.find(p => p.name === PromptNames.targetFolder);
+        expect(targetFolderPrompt).toBeDefined();
+        expect(targetFolderPrompt).toHaveProperty('type', 'input');
+        expect(targetFolderPrompt).toHaveProperty('message', t('prompts.targetPath.message'));
+        expect(targetFolderPrompt).toHaveProperty('guiOptions', {
+            mandatory: true,
+            applyDefaultWhenDirty: true,
+            breadcrumb: t('prompts.targetPath.breadcrumb')
+        });
+        expect(await (targetFolderPrompt as any)?.when(selectedAnswer)).toBe(true);
+        expect(await (targetFolderPrompt as any)?.validate('someother/path', appValue)).toBe(true);
     });
 });
-
-
+                       
\ No newline at end of file
diff --git a/packages/repo-app-import-sub-generator/test/utils/download-utils.test.ts b/packages/repo-app-import-sub-generator/test/utils/download-utils.test.ts
index 12ec89ba44e..aa05469ded7 100644
--- a/packages/repo-app-import-sub-generator/test/utils/download-utils.test.ts
+++ b/packages/repo-app-import-sub-generator/test/utils/download-utils.test.ts
@@ -1,9 +1,10 @@
-import { downloadApp, extractZip } from '../../src/utils/download-utils';
+import { downloadApp, extractZip, hasQfaJson } from '../../src/utils/download-utils';
 import AdmZip from 'adm-zip';
 import { join } from 'path';
-import { t } from '../../src/utils/i18n';
 import RepoAppDownloadLogger from '../../src/utils/logger';
-import * as PromptState from '../../src/prompts/prompt-state';
+import { PromptState } from '../../src/prompts/prompt-state';
+import { qfaJsonFileName } from '../../src/utils/constants';
+import type { AbapServiceProvider } from '@sap-ux/axios-extension';
 
 jest.mock('adm-zip');
 jest.mock('../../src/utils/logger', () => ({
@@ -12,100 +13,104 @@ jest.mock('../../src/utils/logger', () => ({
     }
 }));
 
-describe('extractZip', () => {
-    let mockZip: any;
-    let mockEntry1: any;
-    let mockEntry2: any;
-    let mockFs: any;
-
+describe('App Download Utils', () => {
     beforeEach(() => {
+        PromptState.reset();
         jest.clearAllMocks();
-        mockEntry1 = {
-            isDirectory: false,
-            entryName: 'file1.txt',
-            getData: jest.fn(() => Buffer.from('File 1 content'))
-        };
-        mockEntry2 = {
-            isDirectory: false,
-            entryName: 'folder/file2.txt',
-            getData: jest.fn(() => Buffer.from('File 2 content'))
-        };
-        mockZip = {
-            getEntries: jest.fn(() => [mockEntry1, mockEntry2])
-        };
-
-        (AdmZip as jest.Mock).mockImplementation(() => mockZip);
-        mockFs = {
-            write: jest.fn()
-        };
     });
 
-    it('should extract files from zip and write them using fs', async () => {
-        const extractedPath = join('/tmp/project');
-        const dummyBuffer = Buffer.from('fake zip content');
-
-        await extractZip(extractedPath, dummyBuffer, mockFs);
-
-        expect(mockZip.getEntries).toHaveBeenCalled();
-        expect(mockFs.write).toHaveBeenCalledWith(
-            join(extractedPath, 'file1.txt'),
-            'File 1 content'
-        );
-        expect(mockFs.write).toHaveBeenCalledWith(
-            join(extractedPath, 'folder/file2.txt'),
-            'File 2 content'
-        );
-    });
+    describe('hasQfaJson', () => {
+        it('should return true when qfa.json is present in zip', () => {
+            const mockEntry = { entryName: qfaJsonFileName };
+            const mockZip = { getEntries: jest.fn(() => [mockEntry]) };
+            PromptState.admZip = Buffer.from('dummy');
+            (PromptState.admZip as any).getEntries = mockZip.getEntries;
 
-    it('should log an error if zip extraction fails', async () => {
-        const errorMessage = 'Zip corrupted!';
-        (AdmZip as jest.Mock).mockImplementation(() => {
-            throw new Error(errorMessage);
+            const result = hasQfaJson();
+            expect(result).toBe(true);
         });
 
-        const dummyBuffer = Buffer.from('broken zip');
-        await extractZip('/tmp/fail', dummyBuffer, mockFs);
+        it('should return false when qfa.json is not present', () => {
+            const mockZip = { getEntries: jest.fn(() => [{ entryName: 'other.json' }]) };
+
+            PromptState.admZip = Buffer.from('dummy');
+            (PromptState.admZip as any).getEntries = mockZip.getEntries;
 
-        expect(RepoAppDownloadLogger.logger.error).toHaveBeenCalledWith(
-            t('error.appDownloadErrors.zipExtractionError', { error: errorMessage })
-        );
+            const result = hasQfaJson();
+            expect(result).toBe(false);
+        });
     });
-});
 
-describe('downloadApp', () => {
-    const mockDownloadFiles = jest.fn();
-    const mockGetUi5AbapRepository = jest.fn(() => ({
-        downloadFiles: mockDownloadFiles
-    }));
-    const mockServiceProvider = {
-        getUi5AbapRepository: mockGetUi5AbapRepository
-    };
+    describe('extractZip', () => {
+        const mockFs = {
+            write: jest.fn()
+        };
 
-    beforeEach(() => {
-        jest.clearAllMocks();
-        mockDownloadFiles.mockReset();
+        it('should extract files from zip to provided path', async () => {
+            const mockZipEntries = [
+                {
+                    isDirectory: false,
+                    entryName: 'file1.txt',
+                    getData: jest.fn(() => Buffer.from('file content'))
+                },
+                {
+                    isDirectory: true,
+                    entryName: 'folder/',
+                    getData: jest.fn()
+                }
+            ];
+
+            PromptState.admZip = Buffer.from('dummy');
+            (PromptState.admZip as any).getEntries = jest.fn(() => mockZipEntries);
+
+            await extractZip('/tmp/project', mockFs as any);
+
+            expect(mockFs.write).toHaveBeenCalledWith(
+                join('/tmp/project', 'file1.txt'),
+                'file content'
+            );
+            expect(mockFs.write).toHaveBeenCalledTimes(1);
+        });
 
-        PromptState.PromptState.systemSelection = {
-            connectedSystem: {
-                serviceProvider: mockServiceProvider
-            }
-        } as any;
-    });
+        it('should log error on exception', async () => {
+            const erroringZip = {
+                getEntries: jest.fn(() => {
+                    throw new Error('zip failed');
+                })
+            };
 
-    it('should download app and store it in PromptState', async () => {
-        const mockPackage = { name: 'app-1', files: ['files.js'] };
-        mockDownloadFiles.mockResolvedValue(mockPackage);
+            PromptState.admZip = Buffer.from('dummy');
+            (PromptState.admZip as any).getEntries = erroringZip.getEntries;
 
-        await downloadApp('repo-1');
+            await extractZip('/tmp/project', mockFs as any);
 
-        expect(mockServiceProvider.getUi5AbapRepository).toHaveBeenCalled();
-        expect(mockDownloadFiles).toHaveBeenCalledWith('repo-1');
-        expect(PromptState.PromptState.downloadedAppPackage).toEqual(mockPackage);
+            expect(RepoAppDownloadLogger.logger.error).toHaveBeenCalledWith(
+                expect.stringContaining('zipExtractionError')
+            );
+        });
     });
 
-    it('should throw if serviceProvider is undefined', async () => {
-        PromptState.PromptState.systemSelection = undefined as any;
+    describe('downloadApp', () => {
+        it('should download and assign zip buffer to PromptState', async () => {
+            const mockZipBuffer = Buffer.from('mock zip content');
+
+            const mockDownload = jest.fn().mockResolvedValue(mockZipBuffer);
+            const mockServiceProvider = {
+                getUi5AbapRepository: jest.fn(() => ({
+                    downloadFiles: mockDownload
+                }))
+            };
+
+            PromptState.systemSelection = {
+                connectedSystem: {
+                    serviceProvider: mockServiceProvider as unknown as AbapServiceProvider
+                }
+            };
 
-        await expect(downloadApp('repo-1')).rejects.toThrow();
+            await downloadApp('Z_TEST_REPO');
+
+            expect(mockDownload).toHaveBeenCalledWith('Z_TEST_REPO');
+            expect(PromptState.admZip).toBeInstanceOf(AdmZip);
+        });
     });
 });
diff --git a/packages/repo-app-import-sub-generator/test/utils/validators.test.ts b/packages/repo-app-import-sub-generator/test/utils/validators.test.ts
index 74d5f6f5875..ba5cee8f539 100644
--- a/packages/repo-app-import-sub-generator/test/utils/validators.test.ts
+++ b/packages/repo-app-import-sub-generator/test/utils/validators.test.ts
@@ -2,12 +2,14 @@ import { validateQfaJsonFile, validateAppSelection,isValidPromptState } from '..
 import type { QfaJsonConfig, QuickDeployedAppConfig, AppInfo } from '../../src/app/types';
 import { t } from '../../src/utils/i18n';
 import RepoAppDownloadLogger from '../../src/utils/logger';
-import { downloadApp } from '../../src/utils/download-utils';
+import { downloadApp, hasQfaJson } from '../../src/utils/download-utils';
 import type { AppIndex } from '@sap-ux/axios-extension'
 import { ErrorHandler, ERROR_TYPE } from '@sap-ux/inquirer-common';
 import { HELP_NODES } from '@sap-ux/guided-answers-helper';
 import { PromptState } from '../../src/prompts/prompt-state';
 import type { AbapServiceProvider } from '@sap-ux/axios-extension';
+import { qfaJsonFileName } from '../../src/utils/constants';
+import { AppWizard, MessageType } from '@sap-devx/yeoman-ui-types';
 
 jest.mock('../../src/utils/logger', () => ({
     logger: {
@@ -16,7 +18,8 @@ jest.mock('../../src/utils/logger', () => ({
 }));
 
 jest.mock('../../src/utils/download-utils', () => ({
-    downloadApp: jest.fn()
+    downloadApp: jest.fn(),
+    hasQfaJson: jest.fn(() => true)
 }));
 
 jest.mock('@sap-ux/inquirer-common', () => ({
@@ -167,11 +170,25 @@ describe('validateAppSelection', () => {
         expect(result).toBe(mockHelpLink);
     });
 
+    it('should notify the user that the app contains no qfa.json file in it', async () => {
+        const mockAppWizard = {
+            setHeaderTitle: jest.fn(),
+            showWarning: jest.fn(),
+            showError: jest.fn(),
+            showInformation: jest.fn()
+        } as unknown as AppWizard;
+        const appList: AppIndex = [{ appId: '12345', repoName: 'testRepo' }];
+        const answers = { appId: '12345', repoName: 'testRepo' } as AppInfo;
+        (hasQfaJson as jest.Mock).mockReturnValue(false);
+        await validateAppSelection(answers, appList, undefined, mockAppWizard);
+        expect(mockAppWizard.showError).toHaveBeenCalledWith(t('error.qfaJsonNotFound', { jsonFileName: qfaJsonFileName }), MessageType.notification);
+    });
+
     it('should return true if a valid app is selected and download is successful', async () => {
         const appList: AppIndex = [{ appId: '12345', repoName: 'testRepo' }];
         const answers = { appId: '12345', repoName: 'testRepo' } as AppInfo;
         mockDownloadApp.mockResolvedValue(undefined);
-
+        (hasQfaJson as jest.Mock).mockReturnValue(true);
         const result = await validateAppSelection(answers, appList);
 
         expect(mockDownloadApp).toHaveBeenCalledWith('testRepo');


Reviews:
user: login: lfindlaysap
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T10:32:00Z
user: login: cianmSAP
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T11:23:25Z
user: login: cianmSAP
body: Comments addressed
Updates look good 
Additional tests added
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T11:59:33Z
user: login: devinea
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T13:10:24Z
user: login: lfindlaysap
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T13:43:07Z
user: login: lfindlaysap
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T13:43:15Z
user: login: devinea
body: Thanks @kjose90 for the updates. 
Changes look good and excellent test coverage. 
Changeset ✅ 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-25T09:17:40Z
user: login: lfindlaysap
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T09:25:24Z
user: login: cianmSAP
body: Updates look good
Changeset is appropriate
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T09:33:13Z

Review comments:
diff_hunk: @@ -1,7 +1,7 @@
 {
     "error": {
         "telemetry": "Failed to send telemetry data after downloading the application from the SAPUI5 ABAP Repository. {{- error}}",
-        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Check the file exists.",
+        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Ensure the application to be downloaded is properly deployed from ABAP repository.",
path: packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json
user: login: lfindlaysap
body: ```suggestion
        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Ensure the downloaded application is properly deployed from the ABAP repository.",
```
created_at: 2025-04-23T10:32:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -365,5 +447,200 @@ describe('Repo App Download', () => {
 	
 		expect((generator as any)._runNpmInstall).toHaveBeenCalled();
 	});
+
+	it('should abort the sub generator if the app to be downloaded dosent include qfa.json', async () => {
+		const yeomanEnv = env.createEnv();
+		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath, true);
+		(isValidPromptState as jest.Mock).mockReturnValue(true);
+		
+		await expect( 
+			yeomanTest
+				.run(RepoAppDownloadGenerator, { 
+					resolved: repoAppDownloadGenPath
+				})
+				.cd('.')
+				.withOptions({ 
+					appRootPath: testOutputDir, 
+					appWizard: mockAppWizard,
+					vscode: mockVSCode, 
+					logger: {},
+					data: {
+						postGenCommand: 'test-post-gen-command'
+					}
+				})
+				.withPrompts({
+					systemSelection: 'system3',
+					selectedApp: {
+						appId: appConfig.app.id,
+						title: appConfig.app.title,
+						description: appConfig.app.description,
+						repoName: 'app-1-repo',
+						url: 'url-1'
+					},
+					targetFolder: testOutputDir
+				})
+		)
+		.resolves.not.toThrow();
+		expect(getAppConfig as jest.Mock).not.toHaveBeenCalled();
+		expect(createLaunchConfig as jest.Mock).not.toHaveBeenCalled();
+		expect(handleWorkspaceConfig as jest.Mock).not.toHaveBeenCalled();
+		expect(updateWorkspaceFoldersIfNeeded as jest.Mock).not.toHaveBeenCalled();
+		expect(mockAppWizard.showError).toHaveBeenCalledWith(t('error.qfaJsonNotFound', { jsonFileName: qfaJsonFileName }), MessageType.notification);
+		expect(mockAppWizard.showInformation).not.toHaveBeenCalledWith();
+	});
+
+	it('should abort the sub generator if the prompting is invalid', async () => {
+		const yeomanEnv = env.createEnv();
+		copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath, true);
+		(isValidPromptState as jest.Mock).mockReturnValue(false);
+		
+		await expect( 
+			yeomanTest
+				.run(RepoAppDownloadGenerator, { 
+					resolved: repoAppDownloadGenPath
+				})
+				.cd('.')
+				.withOptions({ 
+					appRootPath: testOutputDir, 
+					appWizard: mockAppWizard,
+					vscode: mockVSCode, 
+					logger: {},
+					data: {
+						postGenCommand: 'test-post-gen-command'
+					}
+				})
+				.withPrompts({
+					systemSelection: 'system3',
+					selectedApp: {
+						appId: appConfig.app.id,
+						title: appConfig.app.title,
+						description: appConfig.app.description,
+						repoName: 'app-1-repo',
+						url: 'url-1'
+					},
+					targetFolder: testOutputDir
+				})
+		)
+		.resolves.not.toThrow();
+		expect(extractZip as jest.Mock).not.toHaveBeenCalled();
+		expect(getAppConfig as jest.Mock).not.toHaveBeenCalled();
+		expect(createLaunchConfig as jest.Mock).not.toHaveBeenCalled();
+		expect(handleWorkspaceConfig as jest.Mock).not.toHaveBeenCalled();
+		expect(updateWorkspaceFoldersIfNeeded as jest.Mock).not.toHaveBeenCalled();
+		expect(mockAppWizard.showInformation).not.toHaveBeenCalledWith();
+	});
+
+	it('should successfully download a quick deployed app from repository', async () => {
+		const yeomanEnv = env.createEnv();
+
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		// mock installation
+		(generator as any).spawnCommand = jest.fn().mockResolvedValue(undefined);
+		await generator.install();
+		expect((generator as any).spawnCommand).toHaveBeenCalled();
+	});
+
+	it('should log an error if installation of a quick deployed app from repository', async () => {
+		const yeomanEnv = env.createEnv();
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		// mock installation
+		(generator as any).spawnCommand = jest.fn().mockRejectedValue(
+			new Error('Installation error')
+		);
+		await generator.install();
+		expect(RepoAppDownloadLogger.logger.error).toHaveBeenCalledWith(
+			t('error.installationErrors.npmInstall', { error: 'Error: Installation error' })
+		);
+	});
+
+	it('should set conflicter.force to true and initialize telemetry', async () => {
+		const yeomanEnv = env.createEnv();
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		// Mock Yeoman environment with a conflicter object
+		(generator.env as any) = {
+			conflicter: { force: false }
+		};
+
+		await generator.initializing();
+
+		expect((generator.env as any).conflicter.force).toBe(true);
+		expect(TelemetryHelper.initTelemetrySettings).toHaveBeenCalledWith(
+			expect.objectContaining({
+				consumerModule: expect.objectContaining({
+					name: expect.any(String),
+					version: expect.any(String)
+				}),
+				internalFeature: expect.any(Boolean),
+				watchTelemetrySettingStore: false
+			})
+		);
+	});
+
+	it('should set callback function on prompts if prompts exist', () => {
+		const yeomanEnv = env.createEnv();
+		const generator = new RepoAppDownloadGenerator([], {
+			env: yeomanEnv,
+			appWizard: mockAppWizard as AppWizard,
+			logger: {},
+			vscode: mockVSCode,
+			skipInstall: false,
+			data: {
+				quickDeployedAppConfig: {
+					appId: appConfig.app.id,
+					serviceProviderInfo: { name: 'system3' }
+				}
+			}
+		});
+		Object.defineProperty(generator, 'prompts', {
+			value: {
+				setCallback: jest.fn()
+			},
+			writable: false
+		});
+		const mockFn = jest.fn();
+		generator.setPromptsCallback(mockFn);
+
+		expect(generator['prompts'].setCallback).toHaveBeenCalledWith(mockFn);
+	});
+
 });
-  
\ No newline at end of file
+
+//index.ts          |   98.19 |    75.67 |   84.61 |   98.19 | 87-88   
path: packages/repo-app-import-sub-generator/test/app.test.ts
user: login: cianmSAP
body: Can be removed? I assume it was mistakenly committed
created_at: 2025-04-23T11:23:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,7 @@
 {
     "error": {
         "telemetry": "Failed to send telemetry data after downloading the application from the SAPUI5 ABAP Repository. {{- error}}",
-        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Check the file exists.",
+        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Ensure the application to be downloaded is properly deployed from ABAP repository.",
path: packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json
user: login: devinea
body: `Ensure the downloaded application is properly deployed from the ABAP repository.`
I think this should be 
`Ensure the downloaded application is properly deployed to the ABAP repository from ABAP Developer tools`
or 
`Ensure the downloaded application is properly deployed to the ABAP repository`
created_at: 2025-04-23T13:10:24Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,7 @@
 {
     "error": {
         "telemetry": "Failed to send telemetry data after downloading the application from the SAPUI5 ABAP Repository. {{- error}}",
-        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Check the file exists.",
+        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Ensure the downloaded application is properly deployed from the ABAP repository.",
path: packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json
user: login: lfindlaysap
body: ```suggestion
        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Ensure the downloaded application is properly deployed to the ABAP repository.",
```
created_at: 2025-04-23T13:43:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,7 @@
 {
     "error": {
         "telemetry": "Failed to send telemetry data after downloading the application from the SAPUI5 ABAP Repository. {{- error}}",
-        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Check the file exists.",
+        "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded application. Ensure the application to be downloaded is properly deployed from ABAP repository.",
path: packages/repo-app-import-sub-generator/src/translations/repo-app-import-sub-generator.i18n.json
user: login: lfindlaysap
body: Thanks, @devinea. I've gone with your second proposal and created a suggestion, @kjose90.
 
created_at: 2025-04-23T13:43:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-23T09:27:29Z
body: ###  🦋  Changeset detected

Latest commit: f9cc56fa6c477869e3049b1f6b70cf493dbf7a53

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                                  | Type  |
| ------------------------------------- | ----- |
| @sap-ux/repo-app-import-sub-generator | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/SAP/open-ux-tools/new/repo-app-import-tests?filename=.changeset/chilled-meals-share.md&value=---%0A%22%40sap-ux%2Frepo-app-import-sub-generator%22%3A%20patch%0A---%0A%0AImprove%20test%20coverage%20and%20add%20App%20Validation%20for%20%60%40sap-ux%2Frepo-app-import-sub-generator%60%0A)


author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-25T09:39:46Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=SAP_open-ux-tools&pullRequest=3179) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=SAP_open-ux-tools&pullRequest=3179&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=SAP_open-ux-tools&pullRequest=3179&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=SAP_open-ux-tools&pullRequest=3179&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=SAP_open-ux-tools&pullRequest=3179&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=SAP_open-ux-tools&pullRequest=3179&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=SAP_open-ux-tools&pullRequest=3179)


author_association: NONE