diff --git a/applicationFE/scripts/test-builtin-helm.mjs b/applicationFE/scripts/test-builtin-helm.mjs new file mode 100644 index 0000000..c1884c1 --- /dev/null +++ b/applicationFE/scripts/test-builtin-helm.mjs @@ -0,0 +1,66 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { ref, computed, watch, nextTick } from 'vue' +import ts from 'typescript' +const source = await readFile(new URL('../src/views/softwareCatalog/components/applicationInstallationForm.vue', import.meta.url), 'utf8') +function between(start, end) { + const first = source.indexOf(start), last = source.indexOf(end, first) + assert.ok(first >= 0 && last > first) + return source.slice(first, last) +} +const code = ts.transpileModule([ + between('const isBuiltInPersistentCatalog =', 'const isJupyterObjectStorageCatalog ='), + between('const supportsStorageClassConfig =', 'const objectStorageEndpointPlaceholder ='), + between('function buildK8sAdditionalConfig()', 'function validateStorageClassSelection()'), + 'return { isBuiltInPersistentCatalog, supportsStorageClassConfig, storageClassRequired, storageClassErrorMessage, buildK8sAdditionalConfig, applyBuiltInPersistentDefaults };' +].join('\n'), { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText +function harness() { + const state = Object.fromEntries(Object.entries({ + selectInfra: 'K8S', selectedCatalogInfo: {}, selectedCatalogChartName: '', isJupyterObjectStorageCatalog: false, + isLokiCatalog: false, ingressData: { ingressEnabled: true }, hpaData: { hpaEnabled: true, hpaMinReplicas: 3 }, + workloadRebalancingEnabled: true, selectedStorageClass: 'standard', storageClassList: [{name: 'standard'}], + storageClassLoading: false, storageClassLoadError: false, storageClassFailure: '', notebookStorageGi: 10, + selectedStorageMinimum: 1, modalTitle: 'Application Installation', showObjectStorageConfig: false, objectStorageData: {enabled:false} + }).map(([key,value]) => [key, ref(value)])) + const env = {...state, computed, watch, hasCatalogCapability: () => false, + _: {isEmpty: value => !value?.length}, buildObjectStorageConfig: () => {throw new Error('Unexpected object storage')}} + return {...state,...new Function(...Object.keys(env),code)(...Object.values(env))} +} +let cases=0 +for (const app of ['redis','mariadb','postgresql','apache','tomcat']) { + for (const target of ['VM','K8S']) { + const h=harness() + h.selectInfra.value=target + h.selectedCatalogChartName.value=app + h.selectedCatalogInfo.value={helmChart:{chartName:app,repositoryName:'mcmp-builtin',chartRepositoryUrl:'classpath:helm',chartVersion:'0.1.0',packageId:'mcmp-builtin-'+app}} + await nextTick() + const persistent=target==='K8S' && ['redis','mariadb','postgresql'].includes(app) + assert.equal(h.isBuiltInPersistentCatalog.value,persistent) + assert.equal(h.storageClassRequired.value,persistent) + if (persistent) { + assert.equal(h.ingressData.value.ingressEnabled,false) + assert.equal(h.hpaData.value.hpaEnabled,false) + assert.equal(h.workloadRebalancingEnabled.value,false) + assert.deepEqual(h.buildK8sAdditionalConfig(),{storageClass:'standard',storageSize:'10Gi',storageAccessMode:'ReadWriteOnce'}) + for (const size of [0,-1,1.5,10000]) { + h.notebookStorageGi.value=size + assert.match(h.storageClassErrorMessage.value,/whole-number/) + cases++ + } + h.notebookStorageGi.value=20; h.selectedStorageMinimum.value=20 + assert.equal(h.storageClassErrorMessage.value,'') + h.notebookStorageGi.value=10 + assert.match(h.storageClassErrorMessage.value,/20/) + h.selectedStorageClass.value='' + assert.match(h.storageClassErrorMessage.value,/StorageClass/) + h.selectedCatalogInfo.value.helmChart.packageId='custom' + assert.equal(h.isBuiltInPersistentCatalog.value,false) + } else { + assert.equal(h.ingressData.value.ingressEnabled,true) + assert.equal(h.hpaData.value.hpaEnabled,true) + assert.equal(h.buildK8sAdditionalConfig(),undefined) + } + cases++ + } +} +console.log(`Built-in Helm form passed (${cases} scenarios plus storage and identity checks).`) diff --git a/applicationFE/scripts/test-vm-clustering.mjs b/applicationFE/scripts/test-vm-clustering.mjs index 7a9a9b7..70061d7 100644 --- a/applicationFE/scripts/test-vm-clustering.mjs +++ b/applicationFE/scripts/test-vm-clustering.mjs @@ -90,7 +90,8 @@ const submitStart = form.indexOf('const runInstall = async () => {') + 'const ru const guardEnd = form.indexOf("\n if (modalTitle.value === 'Application Installation' && (specCheckFlag", submitStart) assert.ok(guardEnd > submitStart) const submitGuard = new Function('modalTitle', 'selectInfra', 'selectDeploymentType', 'canSelectClustering', 'toast', - 'const deploying = { value: false }, deploymentCompleted = { value: false };\n' + transpile(form.slice(submitStart, guardEnd)) + '\nreturn "continue";') + // The separate provider guard is unrelated to clustering eligibility. + 'const deploying = { value: false }, deploymentCompleted = { value: false }, jupyterInstallationUnsupported = { value: false };\n' + transpile(form.slice(submitStart, guardEnd)) + '\nreturn "continue";') const installation = { value: 'Application Installation' } let errors = 0 assert.equal(submitGuard(installation, { value: 'VM' }, { value: 'Clustering' }, { value: false }, { error: () => errors++ }), undefined) diff --git a/applicationFE/src/views/softwareCatalog/components/applicationInstallationForm.vue b/applicationFE/src/views/softwareCatalog/components/applicationInstallationForm.vue index b92fdcc..80b6675 100644 --- a/applicationFE/src/views/softwareCatalog/components/applicationInstallationForm.vue +++ b/applicationFE/src/views/softwareCatalog/components/applicationInstallationForm.vue @@ -549,10 +549,11 @@ -
- +
+

Minimum {{ selectedStorageMinimum }} GiB for the known disk limits. Access mode: ReadWriteOnce. Provider quotas and disk availability are checked during provisioning.

+

Single instance with generated credentials in Secret <release-name>-auth (key: password). Data PVC and credentials are retained after uninstall; remove them separately when no longer needed.

@@ -577,6 +578,7 @@ class="form-check-input" type="checkbox" id="hpaEnabled" + :disabled="isBuiltInPersistentCatalog" v-model="hpaData.hpaEnabled">