Skip to content

Commit

Permalink
Huge fixes: test asserts improvement; updated backup mechanism; proje…
Browse files Browse the repository at this point in the history
…ct refactoring; added spin-up timeout; removed unused package and functions;

Signed-off-by: Vladimir <nlit@pm.me>
  • Loading branch information
CodeLit committed Sep 22, 2023
1 parent d2afd80 commit bc81482
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 118 deletions.
4 changes: 2 additions & 2 deletions docker-compose-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
- ./tests/logs:/app/logs

- ./tests/projects/test-project:/app/projects/test-project:ro
- ./tests/projects/some-project2:/app/projects/some-project2:ro
- ./tests/projects/single-project:/app/projects/single-project:ro

- ./tests/backups/test-project:/app/backups/test-project
- ./tests/backups/some-project2:/app/backups/some-project2
- ./tests/backups/single-project:/app/backups/single-project
7 changes: 5 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ async function run() {
logger.log(`----- Backups completed -----`);
}

await run();

cron.schedule('0 3 * * *', run);

// Wait 1 second to spin up environment
await new Promise((resolve) => setTimeout(resolve, 1000));

await run();
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"babel-eslint": "^10.1.0",
"babel-loader": "^9.1.3",
"babel-preset-env": "^1.7.0",
"current-week-number": "^1.0.7",
"eslint": "^8.49.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
Expand Down
74 changes: 47 additions & 27 deletions src/class/Asserts.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,73 @@ import assert from 'node:assert';
import path from 'path';

/**
* Checks if a file exists.
* Checks if a folder is not empty.
*
* @param {string} filePath - The path to the file.
* @return {undefined}
* @param {string} folderPath - The path to the folder.
* @return {void} Throws an exception if the folder is empty.
*/
export function fileExists(filePath) {
// Check if the file exists
if (!fs.existsSync(filePath)) {
export function folderIsNotEmpty(folderPath) {
// Get the list of files in the folder
const files = fs.readdirSync(folderPath);
if (files.length === 0) {
throw new assert.AssertionError({
message: `File ${path.basename(filePath)} does not exist.`,
expected: path.basename(filePath),
actual: fs.readdirSync(path.dirname(filePath)).join(', '),
message: `Folder ${folderPath} is empty.`,
});
}
}

/**
* Asserts that the number of files in a given folder is equal to a specified length.
* Clears the contents of a folder.
*
* @param {string} folderPath - The path to the folder.
* @param {number} length - The expected number of files.
* @throws {assert.AssertionError} If the number of files is greater than the specified length.
* @throws {AssertionError} If the folder is not empty.
*/
export function filesLength(folderPath, length) {
// Get the list of files in the folder
export function ensureToClearFolder(folderPath) {
fs.emptyDirSync(folderPath);
const files = fs.readdirSync(folderPath);
if (files.length > length) {
if (files.length !== 0) {
throw new assert.AssertionError({
message: `Folder ${folderPath} does not contain the expected number of files.`,
actual: files.length,
expected: length,
message: `Folder ${folderPath} must be empty.`,
});
}
}

/**
* Checks if a folder is not empty.
* Checks if the given folder structure matches the expected structure.
*
* @param {string} folderPath - The path to the folder.
* @return {void} Throws an exception if the folder is empty.
* @param {Array|string} folders - The folders to check the structure for. If a string is provided, it is converted to an array with a single element.
* @param {Array} structure - The expected structure of the folders.
* @throws {AssertionError} Throws an error if the actual structure does not match the expected structure.
*/
export function folderIsNotEmpty(folderPath) {
// Get the list of files in the folder
const files = fs.readdirSync(folderPath);
if (files.length === 0) {
throw new assert.AssertionError({
message: `Folder ${folderPath} is empty.`,
export function structureMatch(folders, structure) {
if (!Array.isArray(folders)) folders = [folders];

function actualStructure(folderPath) {
let structure = [];
const objects = fs.readdirSync(folderPath);
objects.forEach((obj) => {
const isDir = fs.lstatSync(path.join(folderPath, obj)).isDirectory();
if (isDir) {
const subFolderStructure = actualStructure(path.join(folderPath, obj));
subFolderStructure.forEach((subObj) => {
structure.push(obj + '/' + subObj);
});
} else structure.push(obj);
});
return structure;
}

folders.forEach((folderPath) => {
const actualStructureResult = actualStructure(folderPath);
actualStructureResult.sort();
structure.sort();

if (JSON.stringify(actualStructureResult) !== JSON.stringify(structure)) {
throw new assert.AssertionError({
message: `Folder ${folderPath} does not match the expected structure.`,
actual: actualStructureResult,
expected: structure,
});
}
});
}
37 changes: 17 additions & 20 deletions src/class/FolderBackup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import currentWeekNumber from 'current-week-number';
import { COMPRESSION_LEVEL, tar } from 'zip-a-folder';
import tempDirectory from 'temp-dir';
import randomString from 'randomstring';
Expand Down Expand Up @@ -104,29 +103,27 @@ export default class FolderBackup {

let files = (await this.fm.readDir(pathToBackups)) || [];

let doNotCreate = false;
let oldestFileDate;

for (const file of files) {
let fileDate = file.match(/(?<=bkp_).*?(?=\.tgz)/);
fileDate = new Date(fileDate[0]);
if (!oldestFileDate || fileDate < oldestFileDate) {
oldestFileDate = fileDate;
}
}
let doNotCreate = false;

if (oldestFileDate && oldestFileDate < this.today) {
const todayDays = this.today.getTime() / (1000 * 60 * 60 * 24);
const oldestDays = oldestFileDate.getTime() / (1000 * 60 * 60 * 24);

if (type === 'weekly') {
const week = currentWeekNumber(this.today);
const week2 = currentWeekNumber(fileDate);
if (week === week2) {
doNotCreate = true;
break;
}
} else if (type === 'monthly') {
if (this.today.getMonth() === fileDate.getMonth()) {
doNotCreate = true;
break;
}
} else if (type === 'annually') {
if (this.today.getFullYear() === fileDate.getFullYear()) {
doNotCreate = true;
break;
}
if (type === 'weekly' && todayDays - oldestDays < 7) {
doNotCreate = true;
} else if (type === 'monthly' && todayDays - oldestDays < 30) {
doNotCreate = true;
} else if (type === 'annually' && todayDays - oldestDays < 365) {
doNotCreate = true;
}
}

Expand All @@ -144,7 +141,7 @@ export default class FolderBackup {
filter: this.filter,
});

if (!this.fm.exists(pathToBackup) && !doNotCreate) {
if (!doNotCreate) {
const tmpArchive = path.normalize(tmpArchiveDir + '/temp.tgz');

await tar(tmpBackupDir, tmpArchive, {
Expand Down
182 changes: 120 additions & 62 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,146 @@ import { backupsPath, projectsPath, startBackups } from './src/backups.js';
import path from 'path';
import FolderBackup from './src/class/FolderBackup.js';
import {
fileExists,
filesLength,
ensureToClearFolder,
folderIsNotEmpty,
structureMatch,
} from './src/class/Asserts.js';

// Wait 1 second to spin up environment
await new Promise((resolve) => setTimeout(resolve, 1000));

const synchronously = true;

const someProjectPath = path.normalize(projectsPath + '/some-project2');
const singleProjectPath = path.normalize(projectsPath + '/single-project');
const testProjectPath = path.normalize(projectsPath + '/test-project');
const singleProjectBackupsPath = path.normalize(
backupsPath + '/single-project',
);
const testBackupsPath = path.normalize(backupsPath + '/test-project');
const todayDate = FolderBackup.formatISODate(new Date());
const todayArchive = 'bkp_' + todayDate + '.tgz';

folderIsNotEmpty(someProjectPath);
folderIsNotEmpty(singleProjectPath);
folderIsNotEmpty(testProjectPath);

// There was a backup long time ago...
await startBackups('2020-06-21', synchronously);

// And another backup next day after first
await startBackups('2020-06-22', synchronously);
await startBackups('2021-06-23', synchronously);
await startBackups('2021-08-24', synchronously);

// Current TODAY will rewrite the old backups
await startBackups(null, synchronously);

console.log('\x1b[32m--- Backups are created! ---\x1b[0m');
ensureToClearFolder(singleProjectBackupsPath);
ensureToClearFolder(testBackupsPath);

folderIsNotEmpty(someProjectPath);
folderIsNotEmpty(testProjectPath);
// There was a backup long time ago...
await startBackups('2019-01-01', synchronously);

structureMatch(
[singleProjectBackupsPath, testBackupsPath],
[
'daily/bkp_2019-01-01.tgz',
'weekly/bkp_2019-01-01.tgz',
'monthly/bkp_2019-01-01.tgz',
'annually/bkp_2019-01-01.tgz',
],
);

const someProjectBackupPath = path.normalize(backupsPath + '/some-project2');
ensureToClearFolder(singleProjectBackupsPath);
ensureToClearFolder(testBackupsPath);

folderIsNotEmpty(someProjectBackupPath);
await startBackups('2020-06-21', synchronously);

filesLength(path.normalize(someProjectBackupPath + '/daily'), 1);
filesLength(path.normalize(someProjectBackupPath + '/weekly'), 1);
filesLength(path.normalize(someProjectBackupPath + '/monthly'), 1);
filesLength(path.normalize(someProjectBackupPath + '/annually'), 1);
structureMatch(
[singleProjectBackupsPath, testBackupsPath],
[
'daily/bkp_2020-06-21.tgz',
'weekly/bkp_2020-06-21.tgz',
'monthly/bkp_2020-06-21.tgz',
'annually/bkp_2020-06-21.tgz',
],
);

const todayDate = FolderBackup.formatISODate(new Date());
const todayArchive = todayDate + '.tgz';
// And another backup next day after first
await startBackups('2020-06-22', synchronously);

fileExists(
path.normalize(someProjectBackupPath + '/daily/bkp_' + todayArchive),
);
fileExists(
path.normalize(someProjectBackupPath + '/weekly/bkp_' + todayArchive),
);
fileExists(
path.normalize(someProjectBackupPath + '/monthly/bkp_' + todayArchive),
);
fileExists(
path.normalize(someProjectBackupPath + '/annually/bkp_' + todayArchive),
);
structureMatch(singleProjectBackupsPath, [
'daily/bkp_2020-06-22.tgz',
'weekly/bkp_2020-06-21.tgz',
'monthly/bkp_2020-06-21.tgz',
'annually/bkp_2020-06-21.tgz',
]);

structureMatch(testBackupsPath, [
'daily/bkp_2020-06-21.tgz',
'daily/bkp_2020-06-22.tgz',
'weekly/bkp_2020-06-21.tgz',
'monthly/bkp_2020-06-21.tgz',
'annually/bkp_2020-06-21.tgz',
]);

console.log(
'\x1b[32m--- Asserts for some-project2 are successfully completed! ---\x1b[0m',
);
await startBackups('2021-06-23', synchronously);

const testProjectBackupPath = path.normalize(backupsPath + '/test-project');
structureMatch(singleProjectBackupsPath, [
'daily/bkp_2021-06-23.tgz',
'weekly/bkp_2021-06-23.tgz',
'monthly/bkp_2021-06-23.tgz',
'annually/bkp_2021-06-23.tgz',
]);

structureMatch(testBackupsPath, [
'daily/bkp_2020-06-21.tgz',
'daily/bkp_2020-06-22.tgz',
'daily/bkp_2021-06-23.tgz',
'weekly/bkp_2020-06-21.tgz',
'weekly/bkp_2021-06-23.tgz',
'monthly/bkp_2020-06-21.tgz',
'monthly/bkp_2021-06-23.tgz',
'annually/bkp_2020-06-21.tgz',
'annually/bkp_2021-06-23.tgz',
]);

folderIsNotEmpty(testProjectBackupPath);
await startBackups('2021-08-24', synchronously);

filesLength(path.normalize(testProjectBackupPath + '/daily'), 4);
filesLength(path.normalize(testProjectBackupPath + '/weekly'), 4);
filesLength(path.normalize(testProjectBackupPath + '/monthly'), 3);
filesLength(path.normalize(testProjectBackupPath + '/annually'), 2);
structureMatch(singleProjectBackupsPath, [
'daily/bkp_2021-08-24.tgz',
'weekly/bkp_2021-08-24.tgz',
'monthly/bkp_2021-08-24.tgz',
'annually/bkp_2021-06-23.tgz',
]);

structureMatch(testBackupsPath, [
'daily/bkp_2020-06-21.tgz',
'daily/bkp_2020-06-22.tgz',
'daily/bkp_2021-06-23.tgz',
'daily/bkp_2021-08-24.tgz',
'weekly/bkp_2020-06-21.tgz',
'weekly/bkp_2021-06-23.tgz',
'weekly/bkp_2021-08-24.tgz',
'monthly/bkp_2020-06-21.tgz',
'monthly/bkp_2021-06-23.tgz',
'monthly/bkp_2021-08-24.tgz',
'annually/bkp_2021-06-23.tgz',
'annually/bkp_2021-08-24.tgz',
]);

fileExists(
path.normalize(testProjectBackupPath + '/daily/bkp_' + todayArchive),
);
fileExists(
path.normalize(testProjectBackupPath + '/weekly/bkp_' + todayArchive),
);
fileExists(
path.normalize(testProjectBackupPath + '/monthly/bkp_' + todayArchive),
);
fileExists(
path.normalize(testProjectBackupPath + '/annually/bkp_' + todayArchive),
);
// Current TODAY will rewrite the old backups
await startBackups(null, synchronously);

console.log(
'\x1b[32m--- Asserts for test-project are successfully completed! ---\x1b[0m',
);
structureMatch(singleProjectBackupsPath, [
'daily/' + todayArchive,
'weekly/' + todayArchive,
'monthly/' + todayArchive,
'annually/' + todayArchive,
]);

structureMatch(testBackupsPath, [
'daily/bkp_2020-06-22.tgz',
'daily/bkp_2021-06-23.tgz',
'daily/bkp_2021-08-24.tgz',
'daily/' + todayArchive,
'weekly/bkp_2020-06-21.tgz',
'weekly/bkp_2021-06-23.tgz',
'weekly/bkp_2021-08-24.tgz',
'weekly/' + todayArchive,
'monthly/bkp_2021-06-23.tgz',
'monthly/bkp_2021-08-24.tgz',
'monthly/' + todayArchive,
'annually/bkp_2021-08-24.tgz',
'annually/' + todayArchive,
]);

console.log('\x1b[32m--- All test is successfully completed! ---\x1b[0m');
File renamed without changes.
Loading

0 comments on commit bc81482

Please sign in to comment.