Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion installwizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@
If you choose to use the existing OpenCATS installation, you can always run<br />
the installer again later and choose a different option.<br />
<br />
<input style="float: right;" type="button" class="button" value="Next -->" onclick="if (getCheckedValue(document.getElementsByName('installgroupexists')) == 'current') Installpage_populate('a=resumeParsing'); else {document.getElementById('catsUpToDate').style.display='none';showTextBlock('queryResetDatabase');}" />
<input style="float: right;" type="button" class="button" value="Next -->" onclick="if (getCheckedValue(document.getElementsByName('installgroupexists')) == 'current') Installpage_upgradeExisting(); else {document.getElementById('catsUpToDate').style.display='none';showTextBlock('queryResetDatabase');}" />
</div>
<div id="queryInstallBackup" style="display: none;">
<span style="font-weight:bold;">Loading Data - Restore from Backup</span><br />
Expand Down
21 changes: 19 additions & 2 deletions js/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

var response;
var maxSteps;
var installMaintNextAction = "a=reindexResumes";


function setActiveStep(step)
Expand Down Expand Up @@ -120,8 +121,12 @@
response = http.responseText;

if (response.indexOf("setProgressUpdating") == -1)
{
Installpage_populate("a=reindexResumes");
{
if (http.status == 200)

Check notice on line 125 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L125

Mixed spaces and tabs.
{
Installpage_populate(installMaintNextAction);

Check notice on line 127 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L127

A function with a name starting with an uppercase letter should only be used as a constructor.

Check notice on line 127 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L127

Mixed spaces and tabs.
installMaintNextAction = "a=reindexResumes";

Check notice on line 128 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L128

Mixed spaces and tabs.
}

Check notice on line 129 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L129

Mixed spaces and tabs.
}
else
{
Expand All @@ -141,6 +146,18 @@
);
}

function Installpage_upgradeExisting()

Check warning on line 149 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L149

'Installpage_upgradeExisting' is defined but never used.

Check notice on line 149 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L149

Identifier 'Installpage_upgradeExisting' is not in camel case.
{
Installpage_populate("a=upgradeExisting");

Check notice on line 151 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L151

A function with a name starting with an uppercase letter should only be used as a constructor.
}

function Installpage_upgradeExistingMaint()

Check warning on line 154 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L154

'Installpage_upgradeExistingMaint' is defined but never used.

Check notice on line 154 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L154

Identifier 'Installpage_upgradeExistingMaint' is not in camel case.
{
/* Existing installations must run schema maintenance before later installer questions. */
installMaintNextAction = "a=upgradeExistingMaintComplete";
Installpage_maint();

Check notice on line 158 in js/install.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

js/install.js#L158

A function with a name starting with an uppercase letter should only be used as a constructor.
}

function Installpage_append(postData, message)
{
var htmlObjectID = "subFormBlock";
Expand Down
51 changes: 51 additions & 0 deletions modules/install/ajax/ui.php
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,12 @@
break;

case 'resetDatabase':
@session_name(CATS_SESSION_NAME);
session_start();

unset($_SESSION['existingUpgradeMaintStarted']);
unset($_SESSION['existingUpgradeMaintComplete']);

MySQLConnect();

foreach ($tables as $table => $data)
Expand Down Expand Up @@ -971,6 +977,15 @@
@session_name(CATS_SESSION_NAME);
session_start();

if (isset($_SESSION['existingUpgradeMaintComplete']))
{
unset($_SESSION['existingUpgradeMaintStarted']);
unset($_SESSION['existingUpgradeMaintComplete']);

echo '<script type="text/javascript">Installpage_populate(\'a=reindexResumes\');</script>';
break;
}

if (isset($_SESSION['CATS']))
{
unset($_SESSION['CATS']);
Expand All @@ -987,6 +1002,42 @@
</script>';
break;

case 'upgradeExisting':
@session_name(CATS_SESSION_NAME);
session_start();

$_SESSION['existingUpgradeMaintStarted'] = true;
unset($_SESSION['existingUpgradeMaintComplete']);

if (isset($_SESSION['CATS']))
{
unset($_SESSION['CATS']);
}

if (isset($_SESSION['modules']))
{
unset($_SESSION['modules']);
}

echo '<script type="text/javascript">
showTextBlock(\'installingComponentsMaint\');
setTimeout("Installpage_upgradeExistingMaint();", 2000);
</script>';
break;

case 'upgradeExistingMaintComplete':
@session_name(CATS_SESSION_NAME);
session_start();

if (isset($_SESSION['existingUpgradeMaintStarted']))
{
$_SESSION['existingUpgradeMaintComplete'] = true;
unset($_SESSION['existingUpgradeMaintStarted']);
}

echo '<script type="text/javascript">Installpage_populate(\'a=resumeParsing\');</script>';
break;

case 'reindexResumes':
echo '<script type="text/javascript">
showTextBlock(\'installingComponentsMaintResume\');
Expand Down
274 changes: 274 additions & 0 deletions src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
<?php
use PHPUnit\Framework\TestCase;

class ExistingInstallFlowTest extends TestCase

Check warning on line 4 in src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php#L4

The class ExistingInstallFlowTest has 11 non-getter- and setter-methods. Consider refactoring ExistingInstallFlowTest to keep number of methods under 10.
{
private $uiSource;
private $jsSource;

protected function setUp(): void
{
$root = dirname(__DIR__, 4);

$uiPath = $root . '/modules/install/ajax/ui.php';
$jsPath = $root . '/js/install.js';

$this->assertFileExists($uiPath);
$this->assertFileExists($jsPath);

$this->uiSource = file_get_contents($uiPath);
$this->jsSource = file_get_contents($jsPath);
}

public function testUpgradeExistingActionSetsSessionFlagAndCallsExistingMaint()
{
$block = $this->extractSwitchCase($this->uiSource, 'upgradeExisting');
$this->assertNotEmpty($block, 'case upgradeExisting must exist in ui.php');

$this->assertStringContains(
$block,
"['existingUpgradeMaintStarted']",
'upgradeExisting must set the existingUpgradeMaintStarted session flag'
);

$this->assertStringContains(
$block,
"unset(\$_SESSION['existingUpgradeMaintComplete'])",
'upgradeExisting must clear the existingUpgradeMaintComplete flag'
);

$this->assertMatches(
'/Installpage_upgradeExistingMaint\s*\(\s*\)/',
$block,
'upgradeExisting must invoke Installpage_upgradeExistingMaint()'
);
}

public function testJsUpgradeExistingMaintSetsNextActionThenCallsMaint()
{
$this->assertMatches(
'/function\s+Installpage_upgradeExistingMaint\s*\(/',
$this->jsSource,
'Installpage_upgradeExistingMaint() must be defined in install.js'
);

$fnBody = $this->extractJsFunction($this->jsSource, 'Installpage_upgradeExistingMaint');
$this->assertNotEmpty($fnBody, 'Installpage_upgradeExistingMaint function body must be extractable');

$this->assertMatches(
'/installMaintNextAction\s*=\s*["\']a=upgradeExistingMaintComplete["\']/',
$fnBody,
'Installpage_upgradeExistingMaint must set installMaintNextAction to upgradeExistingMaintComplete'
);

$this->assertMatches(
'/Installpage_maint\s*\(\s*\)/',
$fnBody,
'Installpage_upgradeExistingMaint must call Installpage_maint()'
);

$setPos = $this->firstMatchPosition(
'/installMaintNextAction\s*=/',
$fnBody
);
$callPos = $this->firstMatchPosition(
'/Installpage_maint\s*\(/',
$fnBody
);
$this->assertNotNull($setPos);
$this->assertNotNull($callPos);
$this->assertGreaterThan(
$setPos,
$callPos,
'installMaintNextAction must be set before Installpage_maint() is called'
);
}

public function testUpgradeExistingMaintCompleteResumesInstaller()
{
$block = $this->extractSwitchCase($this->uiSource, 'upgradeExistingMaintComplete');
$this->assertNotEmpty($block, 'case upgradeExistingMaintComplete must exist in ui.php');

$this->assertStringContains(
$block,
"['existingUpgradeMaintComplete']",
'upgradeExistingMaintComplete must reference the existingUpgradeMaintComplete session key'
);

$this->assertMatches(
'/a=resumeParsing/',
$block,
'upgradeExistingMaintComplete must redirect to a=resumeParsing'
);
}

public function testMaintActionSkipsSecondRunAfterExistingUpgrade()
{
$block = $this->extractSwitchCase($this->uiSource, 'maint');
$this->assertNotEmpty($block, 'case maint must exist in ui.php');

$this->assertStringContains(
$block,
"['existingUpgradeMaintComplete']",
'maint must check for the existingUpgradeMaintComplete session flag'
);

$this->assertMatches(
'/unset\s*\(\s*\$_SESSION\s*\[\s*[\'"]existingUpgradeMaintStarted[\'"]\s*\]\s*\)/',
$block,
'maint must unset existingUpgradeMaintStarted'
);

$this->assertMatches(
'/unset\s*\(\s*\$_SESSION\s*\[\s*[\'"]existingUpgradeMaintComplete[\'"]\s*\]\s*\)/',
$block,
'maint must unset existingUpgradeMaintComplete'
);

$this->assertMatches(
'/a=reindexResumes/',
$block,
'maint must continue to a=reindexResumes when skipping existing-upgrade maintenance'
);

$normalMaintPattern = '/Installpage_maint\s*\(\s*\)/';
$this->assertMatches(
$normalMaintPattern,
$block,
'maint must still contain the normal Installpage_maint() call for non-existing-upgrade paths'
);
}

public function testMaintContinuationRequiresSuccessfulHttpStatus()
{
$fnBody = $this->extractJsFunction($this->jsSource, 'Installpage_maint');
$this->assertNotEmpty($fnBody, 'Installpage_maint function body must be extractable');

$statusPos = $this->firstMatchPosition('/http\\.status\\s*==\\s*200/', $fnBody);
$populatePos = $this->firstMatchPosition('/Installpage_populate\\s*\\(\\s*installMaintNextAction\\s*\\)/', $fnBody);
$resetPos = $this->firstMatchPosition('/installMaintNextAction\\s*=\\s*[\'"]a=reindexResumes[\'"]\\s*;/', $fnBody);

$this->assertNotNull($statusPos, 'Installpage_maint must check for a successful HTTP status before continuing');
$this->assertNotNull($populatePos, 'Installpage_maint must continue using installMaintNextAction after successful maintenance');
$this->assertNotNull($resetPos, 'Installpage_maint must reset installMaintNextAction after using it');

$this->assertGreaterThan(
$statusPos,
$populatePos,
'Installpage_maint must check the HTTP status before using installMaintNextAction'
);

$this->assertGreaterThan(
$populatePos,
$resetPos,
'Installpage_maint must reset installMaintNextAction after using it'
);
}

public function testResetDatabaseClearsExistingUpgradeFlags()
{
$block = $this->extractSwitchCase($this->uiSource, 'resetDatabase');
$this->assertNotEmpty($block, 'case resetDatabase must exist in ui.php');

$this->assertMatches(
'/unset\s*\(\s*\$_SESSION\s*\[\s*[\'"]existingUpgradeMaintStarted[\'"]\s*\]\s*\)/',
$block,
'resetDatabase must clear existingUpgradeMaintStarted'
);

$this->assertMatches(
'/unset\s*\(\s*\$_SESSION\s*\[\s*[\'"]existingUpgradeMaintComplete[\'"]\s*\]\s*\)/',
$block,
'resetDatabase must clear existingUpgradeMaintComplete'
);
}

// ------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------

private function extractSwitchCase(string $source, string $caseName): string

Check warning on line 190 in src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php#L190

The method extractSwitchCase() has a Cyclomatic Complexity of 10. The configured cyclomatic complexity threshold is 10.
{
$pattern = '/case\s+[\'"]' . preg_quote($caseName, '/') . '[\'"]\s*:/';
if (!preg_match($pattern, $source, $m, PREG_OFFSET_CAPTURE)) {

Check notice on line 193 in src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php#L193

Avoid variables with short names like $m. Configured minimum length is 2.
return '';
}

$start = $m[0][1];
$bodyStart = $start + strlen($m[0][0]);
$len = strlen($source);
$depth = 0;

for ($i = $bodyStart; $i < $len; $i++) {
$ch = $source[$i];

if ($ch === '{') {
$depth++;
} elseif ($ch === '}') {
$depth--;
if ($depth < 0) {
return substr($source, $start, $i - $start);
}
}

if ($depth === 0 && ($ch === 'c' || $ch === 'd')) {
if (preg_match('/^(?:case\s+[\'"]|default\s*:)/', substr($source, $i))) {
return substr($source, $start, $i - $start);
}
}
}

return substr($source, $start);
}

private function extractJsFunction(string $source, string $funcName): string
{
$pattern = '/function\s+' . preg_quote($funcName, '/') . '\s*\([^)]*\)\s*\{/';
if (!preg_match($pattern, $source, $m, PREG_OFFSET_CAPTURE)) {
return '';
}

$braceStart = strpos($source, '{', $m[0][1]);
$depth = 0;
$i = $braceStart;

Check notice on line 233 in src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php#L233

Avoid variables with short names like $i. Configured minimum length is 2.
$len = strlen($source);

while ($i < $len) {
if ($source[$i] === '{') {
$depth++;
} elseif ($source[$i] === '}') {
$depth--;
if ($depth === 0) {
return substr($source, $braceStart, $i - $braceStart + 1);
}
}
$i++;
}

return '';
}

private function firstMatchPosition(string $pattern, string $subject): ?int
{
if (preg_match($pattern, $subject, $m, PREG_OFFSET_CAPTURE)) {

Check notice on line 253 in src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/OpenCATS/Tests/UnitTests/ExistingInstallFlowTest.php#L253

Avoid variables with short names like $m. Configured minimum length is 2.
return $m[0][1];
}
return null;
}

private function assertStringContains(string $haystack, string $needle, string $message = ''): void
{
$this->assertTrue(
strpos($haystack, $needle) !== false,
$message ?: "Failed asserting that string contains '{$needle}'"
);
}

private function assertMatches(string $pattern, string $string, string $message = ''): void
{
$this->assertTrue(
(bool) preg_match($pattern, $string),
$message ?: "Failed asserting that string matches pattern {$pattern}"
);
}
}
Loading