';
if ($submission !== null) {
@@ -131,7 +129,7 @@ public function upload(Request $request, $project_id)
$file = $request->file('folder_path');
$file_name = $file->getClientOriginalName();
- $folder_path = 'public/nodejs/tmp/submissions/' . $request->user()->id . '/' . $project_title;
+ $folder_path = 'public/tmp/submissions/' . $request->user()->id . '/' . $project_title;
$file->storeAs($folder_path, $file_name);
TemporaryFile::create([
@@ -149,7 +147,7 @@ public function submit(Request $request)
try {
$request->validate([
- 'project_id' => 'required|exists:nodejsDB.projects,id',
+ 'project_id' => 'required|exists:projects,id',
'folder_path' => 'required_without:github_url',
'github_url' => 'required_without:folder_path',
]);
@@ -171,7 +169,7 @@ public function submit(Request $request)
if ($temporary_file) {
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
- $submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
+ $submission->addMedia($path)->toMediaCollection('submissions', 'public_submissions_files');
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
rmdir(storage_path('app/' . $request->folder_path));
}
@@ -337,6 +335,14 @@ public function process(Request $request)
break;
}
}
+
+ // Revalidate status before returning response to ensure consistency
+ if ($isNotHistory && $submission->status === Submission::$PROCESSING) {
+ // Force a fresh check of status based on current step results
+ $submission->getCurrentExecutionStep();
+ $completion_percentage = round($submission->getTotalCompletedSteps() / $submission->getTotalSteps() * 100);
+ }
+
return $this->returnSubmissionResponse(
$isNotHistory ? 'Step ' . $step->executionStep->name . ' is ' . $submission->results->{$step->executionStep->name}->status : "History",
$submission->status,
@@ -345,6 +351,33 @@ public function process(Request $request)
$completion_percentage
);
}
+
+ // If we get here, check one more time if submission should be marked as completed
+ if ($isNotHistory && $submission->status === Submission::$PROCESSING) {
+ $allStepsComplete = true;
+ $anyStepsFailed = false;
+
+ foreach ($submission->getExecutionSteps() as $execStep) {
+ $stepName = $execStep->executionStep->name;
+ if (
+ !isset($submission->results->$stepName) ||
+ $submission->results->$stepName->status === Submission::$PENDING ||
+ $submission->results->$stepName->status === Submission::$PROCESSING
+ ) {
+ $allStepsComplete = false;
+ break;
+ }
+
+ if ($submission->results->$stepName->status === Submission::$FAILED) {
+ $anyStepsFailed = true;
+ }
+ }
+
+ if ($allStepsComplete) {
+ $submission->updateStatus($anyStepsFailed ? Submission::$FAILED : Submission::$COMPLETED);
+ }
+ }
+
return $this->returnSubmissionResponse(
($isNotHistory ? 'Submission is processing meanwhile there is no step to execute' : "History"),
$submission->status,
@@ -399,7 +432,10 @@ public function refresh(Request $request)
}
// Delete temp directory
foreach ($commands as $command) {
- $process = new Process($command, null, null, null, 120);
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+ $process = new Process($command, null, $env, null, 120);
$process->run();
if ($process->isSuccessful()) {
Log::info('Command ' . implode(" ", $command) . ' is successful');
@@ -429,7 +465,7 @@ public function refresh(Request $request)
private function getTempDir($submission)
{
- return storage_path('app/public/nodejs/tmp/submissions/' . $submission->user_id . '/' . $submission->project->title . '/' . $submission->id);
+ return storage_path('app/public/tmp/submissions/' . $submission->user_id . '/' . $submission->project->title . '/' . $submission->id);
}
private function is_dir_empty($dir)
@@ -502,33 +538,51 @@ private function lunchReplacePackageJsonJob($submission, $packageJson, $tempDir,
private function lunchCopyTestsFolderJob($submission, $tempDir, $step)
{
- $testsDir = [
- 'testsDirApi' => $submission->project->getMedia('project_tests_api'),
- 'testsDirWeb' => $submission->project->getMedia('project_tests_web'),
- 'testsDirImage' => $submission->project->getMedia('project_tests_images'),
- ];
- // command 1: [1]cp [2]-r [3]{{testsDir}} [4]{{tempDir}}
+ $testFiles = $step->variables;
$commands = $step->executionStep->commands;
- $step_variables = $step->variables;
- $values = ['{{testsDir}}' => $testsDir, '{{tempDir}}' => $tempDir];
- $commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
$commandsArray = [];
- foreach ($testsDir['testsDirApi'] as $key => $value) {
- $commands[2] = $value->getPath();
- $commands[3] = $tempDir . '/tests/api';
- array_push($commandsArray, $commands);
+
+ $testDirPath = $tempDir . '/tests';
+ if (!is_dir($testDirPath)) {
+ mkdir($testDirPath, 0777, true);
}
- foreach ($testsDir['testsDirWeb'] as $key => $value) {
- $commands[2] = $value->getPath();
- $commands[3] = $tempDir . '/tests/web';
- array_push($commandsArray, $commands);
+
+ foreach ($testFiles as $testFile) {
+ // Parse {{sourceFile}}=media:filename:subfolder
+ $parts = explode("=", $testFile);
+
+ if (isset($parts[1])) {
+ // media:filename:subfolder -> [media, filename, subfolder]
+ $fileConfig = explode(":", $parts[1]);
+
+ $mediaCollection = isset($fileConfig[0]) ? $fileConfig[0] : 'tests';
+ $fileName = isset($fileConfig[1]) ? $fileConfig[1] : '';
+ $subFolder = isset($fileConfig[2]) ? $fileConfig[2] : '';
+
+ $mediaItem = $submission->project->getMedia('project_tests' . ($mediaCollection != 'tests' ? "_$mediaCollection" : ""))
+ ->where('file_name', $fileName)
+ ->first();
+
+ if ($mediaItem) {
+ $destDir = $testDirPath;
+ if (!empty($subFolder)) {
+ $destDir .= '/' . $subFolder;
+ if (!is_dir($destDir)) {
+ mkdir($destDir, 0777, true);
+ }
+ }
+
+ $copyCommand = $commands;
+ $copyCommand[2] = $mediaItem->getPath();
+ $copyCommand[3] = $destDir . '/' . basename($fileName);
+ $commandsArray[] = $copyCommand;
+ } else {
+ Log::warning("Test file not found: {$fileName} in collection project_tests_{$mediaCollection}");
+ }
+ }
}
- foreach ($testsDir['testsDirImage'] as $key => $value) {
- $commands[2] = $value->getPath();
- $commands[3] = $tempDir . '/tests/web/images';
- array_push($commandsArray, $commands);
- }
- dispatch(new CopyTestsFolder($submission, $testsDir, $tempDir, $commandsArray))->onQueue(ExecutionStep::$COPY_TESTS_FOLDER);
+
+ dispatch(new CopyTestsFolder($submission, null, $tempDir, $commandsArray))->onQueue(ExecutionStep::$COPY_TESTS_FOLDER);
}
private function lunchNpmInstallJob($submission, $tempDir, $step)
@@ -543,7 +597,7 @@ private function lunchNpmInstallJob($submission, $tempDir, $step)
private function lunchNpmRunStartJob($submission, $tempDir, $step)
{
$commands = $step->executionStep->commands;
- dispatch_sync(new NpmRunStart($submission, $tempDir, $commands));
+ dispatch(new NpmRunStart($submission, $tempDir, $commands));
}
private function lunchNpmRunTestsJob($submission, $tempDir, $step)
@@ -653,7 +707,7 @@ public function changeSourceCode($submission_id)
$user = Auth::user();
$submission = Submission::where('id', $submission_id)->where('user_id', $user->id)->first();
if ($submission) {
- return view('nodejs.submissions.change_source_code', compact('submission'));
+ return view('submissions.change_source_code', compact('submission'));
}
return redirect()->route('submissions');
}
@@ -662,7 +716,7 @@ public function update(Request $request)
{
try {
$request->validate([
- 'submission_id' => 'required|exists:nodejsDB.submissions,id',
+ 'submission_id' => 'required|exists:submissions,id',
'folder_path' => 'required_without:github_url',
'github_url' => 'required_without:folder_path',
]);
@@ -693,7 +747,7 @@ public function update(Request $request)
if ($temporary_file) {
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
- $submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
+ $submission->addMedia($path)->toMediaCollection('submissions', 'public_submissions_files');
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
rmdir(storage_path('app/' . $request->folder_path));
}
diff --git a/app/Jobs/NodeJS/CloneRepository.php b/app/Jobs/NodeJS/CloneRepository.php
index a2d7d67..3ee5848 100644
--- a/app/Jobs/NodeJS/CloneRepository.php
+++ b/app/Jobs/NodeJS/CloneRepository.php
@@ -12,6 +12,7 @@
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
+use Illuminate\Support\Facades\File;
class CloneRepository implements ShouldQueue
{
@@ -41,8 +42,10 @@ public function handle(): void
Log::info("Cloning repo {$this->repoUrl} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Cloning repo {$this->repoUrl}");
try {
+ $this->prepareTempDirectory();
+
// processing
- $process = new Process($this->command);
+ $process = new Process($this->command, null, null, null, 500);
$process->start();
$process_pid = $process->getPid();
$process->wait();
@@ -71,4 +74,15 @@ private function updateSubmissionStatus(Submission $submission, string $status,
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
+
+ private function prepareTempDirectory(): void
+ {
+ if (File::exists($this->tempDir)) {
+ File::deleteDirectory($this->tempDir);
+ }
+
+ File::ensureDirectoryExists(dirname($this->tempDir), 0755, true);
+
+ Log::info("Created temp directory {$this->tempDir}");
+ }
}
diff --git a/app/Jobs/NodeJS/CopyTestsFolder.php b/app/Jobs/NodeJS/CopyTestsFolder.php
index ac0f422..a2c8c5c 100644
--- a/app/Jobs/NodeJS/CopyTestsFolder.php
+++ b/app/Jobs/NodeJS/CopyTestsFolder.php
@@ -41,36 +41,36 @@ public function handle(): void
Log::info("Copying tests folder to {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Copying tests folder");
try {
- // processing
- if (is_dir($this->tempDir . '/tests')) {
- Log::info("Removing old tests folder from {$this->tempDir}");
- Process::fromShellCommandline("rm -rf {$this->tempDir}/tests")->run();
+ // Ensure tests directory exists
+ if (!is_dir($this->tempDir . '/tests')) {
+ mkdir($this->tempDir . '/tests', 0777, true);
}
- mkdir($this->tempDir . '/tests', 0777, true);
- mkdir($this->tempDir . '/tests/api', 0777, true);
- mkdir($this->tempDir . '/tests/web', 0777, true);
- mkdir($this->tempDir . '/tests/web/images', 0777, true);
- foreach ($this->command as $key => $value) {
- $process = new Process($value);
+
+ // Execute all commands to copy test files
+ foreach ($this->command as $command) {
+ $process = new Process($command);
$process->start();
$process_pid = $process->getPid();
$process->wait();
+
if ($process->isSuccessful()) {
- Log::info("Copied tests {$value[2]} folder to {$value[3]}");
+ Log::info("Copied test file {$command[2]} to {$command[3]}");
} else {
- Log::error("Failed to copying tests {$value[2]} folder to {$value[3]}");
- $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copying tests folder");
+ Log::error("Failed to copy test file {$command[2]} to {$command[3]}");
+ Log::error("Error: " . $process->getErrorOutput());
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copy test files: " . $process->getErrorOutput());
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
Process::fromShellCommandline('kill ' . $process_pid)->run();
throw new \Exception($process->getErrorOutput());
}
}
+
// completed
- Log::info("Copied tests folder to {$this->tempDir}");
- $this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Copied tests folder");
+ Log::info("Copied all test files to {$this->tempDir}");
+ $this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Copied test files successfully");
} catch (\Throwable $th) {
- Log::error("Failed to copying tests folder to {$this->tempDir} " . $th->getMessage());
- $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copying tests folder");
+ Log::error("Failed to copy test files to {$this->tempDir}: " . $th->getMessage());
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copy test files: " . $th->getMessage());
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
diff --git a/app/Jobs/NodeJS/NpmInstall.php b/app/Jobs/NodeJS/NpmInstall.php
index 463a0a0..8187d49 100644
--- a/app/Jobs/NodeJS/NpmInstall.php
+++ b/app/Jobs/NodeJS/NpmInstall.php
@@ -41,7 +41,10 @@ public function handle(): void
try {
// processing
// check if the module is already exist within the assets folder
- $process = new Process($this->command, $this->tempDir, null, null, 120);
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+ $process = new Process($this->command, $this->tempDir, $env, null, 120);
$process->start();
$process_pid = $process->getPid();
$process->wait();
diff --git a/app/Jobs/NodeJS/NpmRunStart.php b/app/Jobs/NodeJS/NpmRunStart.php
index efa09b8..1f62453 100644
--- a/app/Jobs/NodeJS/NpmRunStart.php
+++ b/app/Jobs/NodeJS/NpmRunStart.php
@@ -36,12 +36,14 @@ public function __construct($submission, $tempDir, $command)
*/
public function handle(): void
{
+ set_time_limit(60);
+
$submission = $this->submission;
$tempDir = $this->tempDir;
$command = $this->command;
- Log::info("NPM run start is processing in folder {$this->tempDir}");
+ Log::info("NPM run will run on folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM run start is processing");
// Change port number in .env file
$port = $this->getAvailablePort();
@@ -49,6 +51,7 @@ public function handle(): void
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to find an available port for the project");
return;
}
+ Log::info("NPM run start is processing on port $port");
$submission->updatePort($port);
// Change port number in .env file
$envPath = "$tempDir/.env";
@@ -60,21 +63,23 @@ public function handle(): void
// Run NPM start command
try {
- $process = new Process($command, $tempDir, null, null, null);
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+ $process = new Process($command, $tempDir, $env, null, null);
$process->start();
$process->waitUntil(function ($type, $output) use ($port) {
- return strpos($output, "Server started on port $port") !== false || strpos($output, "MongoNetworkError") !== false;
+ return strpos($output, "$port") !== false || strpos($output, "MongoNetworkError") !== false;
}, 60000); // Wait for 60 seconds
- if (strpos($process->getOutput(), "Server started on port $port") !== false) {
+ if (strpos($process->getOutput(), "$port") !== false) {
Log::info("NPM run start is completed in folder {$tempDir} the application is running on port $port");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, $process->getOutput());
- $process->wait();
} else {
Log::error("Failed to NPM run start in folder {$tempDir} due to error " . $process->getOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to start application on port $port");
- Process::fromShellCommandline("npx kill-port $port")->run();
}
+ Process::fromShellCommandline("npx kill-port $port")->run();
} catch (ProcessTimedOutException $th) {
$process->stop();
Log::error("Failed to NPM run start in folder {$tempDir} due to timeout " . $process->getOutput());
diff --git a/app/Jobs/NodeJS/NpmRunTests.php b/app/Jobs/NodeJS/NpmRunTests.php
index ae32964..0d6d34e 100644
--- a/app/Jobs/NodeJS/NpmRunTests.php
+++ b/app/Jobs/NodeJS/NpmRunTests.php
@@ -47,19 +47,69 @@ public function handle(): void
Log::info("Running {$command_string} in folder {$this->tempDir}");
$this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$PROCESSING, "Running");
usleep(100000);
- $process = new Process($command, $this->tempDir, null, null, 120);
- $process->start();
- $process_pid = $process->getPid();
- $process->wait();
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+
+ $process = new Process($command, $this->tempDir, $env, null, 120);
+ $process->setTty(false);
+ $process->setPty(false);
+ $output = '';
+ $errorOutput = '';
+
+ $process->run(function ($type, $buffer) use (&$output, &$errorOutput) {
+ if (Process::OUT === $type) {
+ $output .= $buffer;
+ } else {
+ $errorOutput .= $buffer;
+ }
+ });
+
+ $fullOutput = trim($output . "\n" . $errorOutput);
+
+ $passedTests = null;
+ $totalTests = null;
+ $failedTests = null;
+
+ // Handle format: "Tests: X failed, Y passed, Z total"
+ if (preg_match('/Tests:\s+(\d+)\s+failed,\s+(\d+)\s+passed,\s+(\d+)\s+total/', $fullOutput, $matches)) {
+ $failedTests = (int)$matches[1];
+ $passedTests = (int)$matches[2];
+ $totalTests = (int)$matches[3];
+ Log::info("Extracted test metrics: $failedTests failed, $passedTests passed, $totalTests total");
+ }
+ // Handle format: "Tests: X passed, Y total"
+ else if (preg_match('/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+total/', $fullOutput, $matches)) {
+ $passedTests = (int)$matches[1];
+ $totalTests = (int)$matches[2];
+ $failedTests = $totalTests - $passedTests;
+ Log::info("Extracted test metrics: $passedTests passed, $totalTests total");
+ }
+
if ($process->isSuccessful()) {
$pass_all[$key] = true;
- Log::info("{$command_string} in folder {$this->tempDir}");
- $this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$COMPLETED, "Completed");
+ Log::info("{$command_string} completed in folder {$this->tempDir}");
+ $this->updateSubmissionTestsResultsStatus(
+ $command_string,
+ $submission,
+ Submission::$COMPLETED,
+ $fullOutput,
+ $passedTests,
+ $totalTests,
+ $failedTests
+ );
} else {
$pass_all[$key] = false;
- Log::error("Failed to NPM run test {$command_string} " . $process->getErrorOutput());
- $this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$FAILED, $process->getErrorOutput());
- Process::fromShellCommandline('kill ' . $process_pid)->run();
+ Log::error("Failed to NPM run test {$command_string}");
+ $this->updateSubmissionTestsResultsStatus(
+ $command_string,
+ $submission,
+ Submission::$FAILED,
+ $fullOutput,
+ $passedTests,
+ $totalTests,
+ $failedTests
+ );
}
}
if (in_array(false, $pass_all) == false) {
@@ -77,10 +127,17 @@ public function handle(): void
}
}
- private function updateSubmissionTestsResultsStatus($testName, Submission $submission, string $status, string $output): void
- {
+ private function updateSubmissionTestsResultsStatus(
+ $testName,
+ Submission $submission,
+ string $status,
+ string $output,
+ ?int $passedTests = null,
+ ?int $totalTests = null,
+ ?int $failedTests = null
+ ): void {
$stepName = ExecutionStep::$NPM_RUN_TESTS;
- $submission->updateOneTestResult($stepName, $testName, $status, $output);
+ $submission->updateOneTestResult($stepName, $testName, $status, $output, $passedTests, $totalTests, $failedTests);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
diff --git a/app/Jobs/NodeJS/UnzipZipFiles.php b/app/Jobs/NodeJS/UnzipZipFiles.php
index 8d9f5f6..71d8e56 100644
--- a/app/Jobs/NodeJS/UnzipZipFiles.php
+++ b/app/Jobs/NodeJS/UnzipZipFiles.php
@@ -31,19 +31,26 @@ public function __construct($submission, $zipFileDir, $tempDir, $command)
$this->tempDir = $tempDir;
$this->command = $command;
}
-
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
+
Log::info("Unzipping {$this->zipFileDir} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Unzipping submitted folder");
+
try {
- if (!file_exists($this->tempDir)) mkdir($this->tempDir, 0777, true);
- // processing
- $process = new Process($this->command);
+ $this->prepareTempDirectory();
+
+ $process = new Process(
+ $this->command,
+ null,
+ $this->getEnvironment(),
+ null,
+ 300
+ );
$process->start();
$process_pid = $process->getPid();
$process->wait();
@@ -51,23 +58,55 @@ public function handle(): void
Log::info("Unzipped {$this->zipFileDir} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Unzipped submitted folder");
} else {
- Log::error("Failed to unzip {$this->zipFileDir} " . $process->getErrorOutput());
- $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to unzip submitted folder");
- Process::fromShellCommandline('kill ' . $process_pid)->run();
- Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
+ $error = "Failed to unzip: " . $process->getErrorOutput() . "\n" . $process->getOutput();
+ Log::error($error);
+ $this->cleanup();
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, $error);
}
} catch (\Throwable $th) {
- // failed
- Log::error("Failed to unzip {$this->zipFileDir} " . $th->getMessage());
- $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed tp unzip submitted folder");
- // Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
+ $error = "Failed to unzip {$this->zipFileDir} " . $th->getMessage();
+ Log::error($error);
+ $this->cleanup();
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, $error);
+ Process::fromShellCommandline('kill ' . $process_pid)->run();
+ Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
+ }
+ }
+
+ protected function prepareTempDirectory(): void
+ {
+ File::ensureDirectoryExists($this->tempDir, 0755);
+
+ File::cleanDirectory($this->tempDir);
+ }
+
+ protected function getEnvironment(): array
+ {
+ return [
+ 'PATH' => '/usr/local/bin:/usr/bin:/bin:' . getenv('PATH'),
+ 'HOME' => getenv('HOME') ?: '/tmp',
+ ];
+ }
+
+ protected function cleanup(): void
+ {
+ try {
+ if (File::exists($this->tempDir)) {
+ File::deleteDirectory($this->tempDir);
+ }
+ } catch (\Throwable $th) {
+ Log::error("Cleanup failed: " . $th->getMessage());
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$UNZIP_ZIP_FILES;
- if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
- if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
+ if ($status !== Submission::$PROCESSING) {
+ $submission->updateOneResult($stepName, $status, $output);
+ }
+ if ($status !== Submission::$COMPLETED) {
+ $submission->updateStatus($status);
+ }
}
}
diff --git a/app/Models/NodeJS/ExecutionStep.php b/app/Models/NodeJS/ExecutionStep.php
index 5992825..a427fcf 100644
--- a/app/Models/NodeJS/ExecutionStep.php
+++ b/app/Models/NodeJS/ExecutionStep.php
@@ -9,8 +9,6 @@ class ExecutionStep extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'name',
'commands',
diff --git a/app/Models/NodeJS/Project.php b/app/Models/NodeJS/Project.php
index f7b7947..2d61cf1 100644
--- a/app/Models/NodeJS/Project.php
+++ b/app/Models/NodeJS/Project.php
@@ -11,8 +11,6 @@ class Project extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'title',
'description',
diff --git a/app/Models/NodeJS/ProjectExecutionStep.php b/app/Models/NodeJS/ProjectExecutionStep.php
index a107f95..1681832 100644
--- a/app/Models/NodeJS/ProjectExecutionStep.php
+++ b/app/Models/NodeJS/ProjectExecutionStep.php
@@ -9,8 +9,6 @@ class ProjectExecutionStep extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'project_id',
'execution_step_id',
diff --git a/app/Models/NodeJS/ProjectsDefaultFileStructure.php b/app/Models/NodeJS/ProjectsDefaultFileStructure.php
index a8336c3..25aed60 100644
--- a/app/Models/NodeJS/ProjectsDefaultFileStructure.php
+++ b/app/Models/NodeJS/ProjectsDefaultFileStructure.php
@@ -3,14 +3,12 @@
namespace App\Models\NodeJS;
use Illuminate\Database\Eloquent\Factories\HasFactory;
-use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Model;;
class ProjectsDefaultFileStructure extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'project_id',
'structure',
diff --git a/app/Models/NodeJS/Submission.php b/app/Models/NodeJS/Submission.php
index b3a21de..e233452 100644
--- a/app/Models/NodeJS/Submission.php
+++ b/app/Models/NodeJS/Submission.php
@@ -10,9 +10,6 @@
class Submission extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
-
- protected $connection = 'nodejsDB';
-
static $types = ['file', 'url'];
static $statues = ['pending', 'processing', 'completed', 'failed'];
static $FILE = 'file';
@@ -158,11 +155,25 @@ public function updateOneResult($step_name, $status, $output)
$this->updateResults($results);
}
- public function updateOneTestResult($step_name, $test_name, $status, $output)
- {
+ public function updateOneTestResult(
+ $step_name,
+ $test_name,
+ $status,
+ $output,
+ ?int $passedTests = null,
+ ?int $totalTests = null,
+ ?int $failedTests = null
+ ) {
$results = $this->results;
$results->$step_name->testResults->$test_name->status = $status;
$results->$step_name->testResults->$test_name->output = $output;
+
+ if ($passedTests !== null && $totalTests !== null) {
+ $results->$step_name->testResults->$test_name->passedTests = $passedTests;
+ $results->$step_name->testResults->$test_name->totalTests = $totalTests;
+ $results->$step_name->testResults->$test_name->failedTests = $failedTests;
+ }
+
$this->updateResults($results);
}
@@ -192,8 +203,12 @@ public function getCurrentExecutionStep($step_id = null)
if (!$results) {
return $current_step;
}
+
foreach ($steps as $step) {
- if ($results->{$step->executionStep->name}?->status == self::$PROCESSING || $results->{$step->executionStep->name}?->status == self::$PENDING) {
+ if (
+ $results->{$step->executionStep->name}?->status == self::$PROCESSING ||
+ $results->{$step->executionStep->name}?->status == self::$PENDING
+ ) {
$current_step = $step;
break;
}
@@ -201,16 +216,29 @@ public function getCurrentExecutionStep($step_id = null)
if (!$current_step) {
$have_failed_steps = false;
+ $all_completed = true;
+
foreach ($steps as $step) {
- if ($results->{$step->executionStep->name}?->status == self::$FAILED) {
- $have_failed_steps = true;
+ if (
+ !isset($results->{$step->executionStep->name}) ||
+ $results->{$step->executionStep->name}?->status == self::$PENDING ||
+ $results->{$step->executionStep->name}?->status == self::$PROCESSING
+ ) {
+ $all_completed = false;
break;
}
+
+ if ($results->{$step->executionStep->name}?->status == self::$FAILED) {
+ $have_failed_steps = true;
+ }
}
- if ($have_failed_steps) {
- $this->updateStatus(self::$FAILED);
- } else {
- $this->updateStatus(self::$COMPLETED);
+
+ if ($all_completed) {
+ if ($have_failed_steps) {
+ $this->updateStatus(self::$FAILED);
+ } else {
+ $this->updateStatus(self::$COMPLETED);
+ }
}
}
diff --git a/app/Models/NodeJS/SubmissionHistory.php b/app/Models/NodeJS/SubmissionHistory.php
index abe2b92..f6298f6 100644
--- a/app/Models/NodeJS/SubmissionHistory.php
+++ b/app/Models/NodeJS/SubmissionHistory.php
@@ -8,9 +8,6 @@
class SubmissionHistory extends Model
{
use HasFactory;
-
- protected $connection = 'nodejsDB';
-
static $types = ['file', 'url'];
static $statues = ['pending', 'processing', 'completed', 'failed'];
static $FILE = 'file';
diff --git a/app/Models/NodeJS/TemporaryFile.php b/app/Models/NodeJS/TemporaryFile.php
index 22f0ab3..42d737a 100644
--- a/app/Models/NodeJS/TemporaryFile.php
+++ b/app/Models/NodeJS/TemporaryFile.php
@@ -9,8 +9,6 @@ class TemporaryFile extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'folder_path',
'file_name',
diff --git a/database/seeders/NodeJS_Seeder.php b/database/seeders/NodeJS_Seeder.php
index cfa595f..82c9439 100644
--- a/database/seeders/NodeJS_Seeder.php
+++ b/database/seeders/NodeJS_Seeder.php
@@ -4,7 +4,6 @@
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
-use App\Models\User;
use Illuminate\Support\Facades\Hash;
use App\Models\NodeJS\Project;
use App\Models\NodeJS\ProjectsDefaultFileStructure;
@@ -18,344 +17,16 @@ class NodeJS_Seeder extends Seeder
*/
public function run(): void
{
- // user
- User::create([
- 'name' => 'Omar',
- 'email' => 'omar.yem1111@gmail.com',
- 'password' => Hash::make('123456789'),
- 'created_at' => now(),
- 'updated_at' => now(),
- ]);
-
- // projects
- Project::insert([[
- 'title' => 'api-experiment',
- 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
- 'tech_stack' => json_encode([
- 'framework' => 'ExpressJS',
- 'language' => 'NodeJS',
- 'database' => 'MongoDB',
- 'testing' => 'Jest, Supertest, Puppeteer',
- ]),
- 'github_url' => 'https://github.com/Omar630603/api-experiment',
- 'image' => 'image',
- 'created_at' => now(),
- 'updated_at' => now(),
- ], [
- 'title' => 'auth-experiment',
- 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
- 'tech_stack' => json_encode([
- 'framework' => 'ExpressJS',
- 'language' => 'NodeJS',
- 'database' => 'MongoDB',
- 'testing' => 'Jest, Supertest, Puppeteer',
- ]),
- 'github_url' => 'https://github.com/Omar630603/auth-experiment',
- 'image' => 'image',
- 'created_at' => now(),
- 'updated_at' => now(),
- ]]);
-
-
- $project_api_experiment = Project::where('title', 'api-experiment')->first();
- $project_auth_experiment = Project::where('title', 'auth-experiment')->first();
-
- // images
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/images/api-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/images/auth-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
-//
-// // files
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-//
-// // tests
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/create-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/no-products-found-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/not-found-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/product-details-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/products-table-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/update-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-password-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page-after-register.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page-with-error.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/profile-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/register-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-//
-// // guides
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-//
-// // supplements
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/initial_data.json'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-//
-// // zips
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-
- $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
- $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
-
- // project default file structure
- ProjectsDefaultFileStructure::insert([
- [
- 'project_id' => $api_experiment_project_id,
- 'structure' => json_encode([
- 'controllers' => [
- 'api' => [
- 'product.controller.js' => '',
- ],
- 'web' => [
- 'product.controller.js' => '',
- ],
- ],
- 'models' => [
- 'product.model.js' => '',
- ],
- 'node_modules' => '',
- 'routes' => [
- 'api' => [
- 'product.routes.js' => '',
- ],
- 'web' => [
- 'product.routes.js' => '',
- ],
- ],
- 'tests' => [
- 'api' => [
- 'testA01.test.js' => '',
- 'testA02.test.js' => '',
- 'testA03.test.js' => '',
- 'testA04.test.js' => '',
- 'testA05.test.js' => '',
- ],
- 'web' => [
- 'images' => [
- 'create-product-page.png' => '',
- 'error-notFound-page.png' => '',
- 'index-page.png' => '',
- 'no-products-found-page.png' => '',
- 'not-found-product-page.png' => '',
- 'product-details-page.png' => '',
- 'products-table-page.png' => '',
- 'update-product-page.png' => '',
- ],
- 'testA01.test.js' => '',
- 'testA02.test.js' => '',
- 'testA03.test.js' => '',
- 'testA04.test.js' => '',
- 'testA05.test.js' => '',
- ],
- ],
- 'web' => [
- 'layouts' => [
- 'main.ejs' => '',
- ],
- 'styles' => [
- 'main.css' => '',
- ],
- 'views' => [
- 'products' => [
- 'create.ejs' => '',
- 'details.ejs' => '',
- 'index.ejs' => '',
- 'update.ejs' => '',
- ],
- 'error.ejs' => '',
- 'index.ejs' => '',
- ],
- ],
- '.env' => '',
- '.env.example' => '',
- '.gitignore' => '',
- 'app.js' => '',
- 'initial_data.json' => '',
- 'package-lock.json' => '',
- 'package.json' => '',
- 'README' => '',
- 'server.js' => '',
- ]),
- 'excluded' => json_encode([
- 'node_modules',
- 'tests',
- '.env',
- '.env.example',
- '.gitignore',
- 'package-lock.json',
- 'initial_data.json',
- 'README',
- ]),
- 'replacements' => json_encode([
- '.env',
- 'tests',
- 'package.json'
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'structure' => json_encode([
- 'controllers' => [
- 'api' => [
- 'auth.controller.js' => '',
- ],
- 'web' => [
- 'auth.controller.js' => '',
- ],
- ],
- 'helpers' => [
- 'errorhandler.helper.js' => '',
- 'jsonwebtoken.helper.js' => '',
- ],
- 'models' => [
- 'user.model.js' => '',
- ],
- 'node_modules' => '',
- 'routes' => [
- 'api' => [
- 'auth.routes.js' => '',
- ],
- 'web' => [
- 'auth.routes.js' => '',
- ],
- ],
- 'services' => [
- 'auth.service.js' => '',
- ],
- 'tests' => [
- 'api' => [
- 'testB01.test.js' => '',
- 'testB02.test.js' => '',
- 'testB03.test.js' => '',
- 'testB04.test.js' => '',
- 'testB05.test.js' => '',
- ],
- 'web' => [
- 'images' => [
- 'edit-page.png' => '',
- 'edit-password-page.png' => '',
- 'error-notFound-page.png' => '',
- 'index-page.png' => '',
- 'index-page-after-register.png' => '',
- 'login-page.png' => '',
- 'login-page-with-error.png' => '',
- 'profile-page.png' => '',
- 'register-page.png' => '',
- ],
- 'testB01.test.js' => '',
- 'testB02.test.js' => '',
- 'testB03.test.js' => '',
- 'testB04.test.js' => '',
- 'testB05.test.js' => '',
- ],
- ],
- 'web' => [
- 'layouts' => [
- 'main.ejs' => '',
- ],
- 'styles' => [
- 'main.css' => '',
- ],
- 'views' => [
- 'auth' => [
- 'edit.ejs' => '',
- 'login.ejs' => '',
- 'profile.ejs' => '',
- 'register.ejs' => '',
- ],
- 'error.ejs' => '',
- 'index.ejs' => '',
- ],
- ],
- '.env' => '',
- '.env.example' => '',
- '.gitignore' => '',
- 'app.js' => '',
- 'package-lock.json' => '',
- 'package.json' => '',
- 'README' => '',
- 'server.js' => '',
- ]),
- 'excluded' => json_encode([
- 'node_modules',
- 'tests',
- '.env',
- '.env.example',
- '.gitignore',
- 'package-lock.json',
- 'README',
- ]),
- 'replacements' => json_encode([
- '.env',
- 'tests',
- 'package.json'
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ]
-
- ]);
-
- // execution steps
+ // Seed Execution Steps
ExecutionStep::insert([
[
'name' => 'Clone Repository',
'commands' => json_encode([
- 'git', 'clone', '{{repoUrl}}', '{{tempDir}}',
+ 'git',
+ 'clone',
+ '--depth=1',
+ '{{repoUrl}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -363,7 +34,11 @@ public function run(): void
[
'name' => 'Unzip ZIP Files',
'commands' => json_encode([
- 'unzip', '{{zipFileDir}}', '-d', '{{tempDir}}',
+ 'unzip',
+ '-o',
+ '{{zipFileDir}}',
+ '-d',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -371,7 +46,9 @@ public function run(): void
[
'name' => 'Remove ZIP Files',
'commands' => json_encode([
- 'rm', '-rf', '{{zipFileDir}}',
+ 'rm',
+ '-rf',
+ '{{zipFileDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -379,7 +56,8 @@ public function run(): void
[
'name' => 'Examine Folder Structure',
'commands' => json_encode([
- 'ls', '{{tempDir}}',
+ 'ls',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -387,7 +65,10 @@ public function run(): void
[
'name' => 'Add .env File',
'commands' => json_encode([
- 'cp', '-r', '{{envFile}}', '{{tempDir}}',
+ 'cp',
+ '-r',
+ '{{envFile}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -395,7 +76,10 @@ public function run(): void
[
'name' => 'Replace package.json',
'commands' => json_encode([
- 'cp', '-r', '{{packageJson}}', '{{tempDir}}',
+ 'cp',
+ '-r',
+ '{{packageJson}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -403,7 +87,10 @@ public function run(): void
[
'name' => "Copy 'tests' Folder",
'commands' => json_encode([
- 'cp', '-r', '{{testsDir}}', '{{tempDir}}',
+ 'cp',
+ '-r',
+ '{{sourceFile}}',
+ '{{destinationFile}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -411,7 +98,9 @@ public function run(): void
[
'name' => 'NPM Install',
'commands' => json_encode([
- 'npm', 'install', '{{options}}',
+ 'npm',
+ 'install',
+ '{{options}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -419,7 +108,9 @@ public function run(): void
[
'name' => 'NPM Run Start',
'commands' => json_encode([
- 'npm', 'run', 'start',
+ 'npm',
+ 'run',
+ 'start',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -427,7 +118,9 @@ public function run(): void
[
'name' => 'NPM Run Tests',
'commands' => json_encode([
- 'npm', 'run', '{{testFile}}',
+ 'npx',
+ 'jest',
+ '{{testFile}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -435,51 +128,121 @@ public function run(): void
[
'name' => 'Delete Temp Directory',
'commands' => json_encode([
- 'rm', '-rf', '{{tempDir}}',
+ 'rm',
+ '-rf',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
]
]);
+
+ // Seed the project
+ Project::insert([
+ [
+ 'title' => 'asynchronous-programming',
+ 'description' => 'Proyek ini adalah pengembangan backend untuk sistem reservasi restoran berbasis Node.js yang dikembangkan secara bertahap untuk mempelajari konsep asynchronous programming dan error handling.
+ Mahasiswa akan memulai dengan setup project, kemudian mengimplementasikan berbagai pola asynchronous seperti callback, Promise, dan Async/Await untuk mengelola menu restoran, reservasi meja, dan pesanan.
+ Selain itu, proyek ini juga mencakup error handling yang efektif menggunakan middleware untuk menangani kesalahan selama operasi database, validasi input, dan proses asynchronous lainnya',
+ 'tech_stack' => json_encode([
+ 'framework' => 'ExpressJS',
+ 'language' => 'NodeJS',
+ 'database' => 'MongoDB',
+ ]),
+ 'github_url' => 'https://github.com/Omar630603/auth-experiment',
+ 'image' => 'image',
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]
+ ]);
- // execution step projects
- $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
- $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
+ $project_asynchronous_programming = Project::where('title', 'asynchronous-programming')->first();
- $clone_repo_execution_step_id = ExecutionStep::where('name', ExecutionStep::$CLONE_REPOSITORY)->first()->id;
- $unzip_zip_files_execution_step_id = ExecutionStep::where('name', ExecutionStep::$UNZIP_ZIP_FILES)->first()->id;
- $checking_folder_structure_execution_step_id = ExecutionStep::where('name', ExecutionStep::$EXAMINE_FOLDER_STRUCTURE)->first()->id;
- $add_env_file_execution_step_id = ExecutionStep::where('name', ExecutionStep::$ADD_ENV_FILE)->first()->id;
- $replace_package_json_execution_step_id = ExecutionStep::where('name', ExecutionStep::$REPLACE_PACKAGE_JSON)->first()->id;
- $copy_tests_folder_step_id = ExecutionStep::where('name', ExecutionStep::$COPY_TESTS_FOLDER)->first()->id;
- $npm_install_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_INSTALL)->first()->id;
- $npm_run_start_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_START)->first()->id;
- $npm_run_tests_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_TESTS)->first()->id;
- $delete_temp_directory_execution_step_id = ExecutionStep::where('name', ExecutionStep::$DELETE_TEMP_DIRECTORY)->first()->id;
+ //images
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/images/asynchronous-programming.png'))->preservingOriginal()->toMediaCollection('project_images', 'nodejs_public_projects_files')->preserve;
+
+ //files
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/files/.env'))->preservingOriginal()->toMediaCollection('project_files', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/files/package.json'))->preservingOriginal()->toMediaCollection('project_files', 'nodejs_public_projects_files');
+
+ //guides
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP01.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP02.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP03.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP04.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP05.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+
+ //supplements
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/supplements/.env.example'))->preservingOriginal()->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/supplements/.gitignore'))->preservingOriginal()->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+
+ //zips
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/zips/guides.zip'))->preservingOriginal()->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/zips/supplements.zip'))->preservingOriginal()->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+
+ //tests
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul1-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul2-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul3-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul4-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul5-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul1-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul2-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul3-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul4-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul5-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+
+ // Get the project ID for associating execution steps
+ $async_experiment_project_id = Project::where('title', 'asynchronous-programming')->first()->id;
+
+ // Get execution step IDs
+ $clone_repo_execution_step_id = ExecutionStep::where('name', 'Clone Repository')->first()->id;
+ $unzip_zip_files_execution_step_id = ExecutionStep::where('name', 'Unzip ZIP Files')->first()->id;
+ $checking_folder_structure_execution_step_id = ExecutionStep::where('name', 'Examine Folder Structure')->first()->id;
+ $add_env_file_execution_step_id = ExecutionStep::where('name', 'Add .env File')->first()->id;
+ $replace_package_json_execution_step_id = ExecutionStep::where('name', 'Replace package.json')->first()->id;
+ $copy_tests_folder_step_id = ExecutionStep::where('name', "Copy 'tests' Folder")->first()->id;
+ $npm_install_execution_step_id = ExecutionStep::where('name', 'NPM Install')->first()->id;
+ $npm_run_start_execution_step_id = ExecutionStep::where('name', 'NPM Run Start')->first()->id;
+ $npm_run_tests_execution_step_id = ExecutionStep::where('name', 'NPM Run Tests')->first()->id;
+ $delete_temp_directory_execution_step_id = ExecutionStep::where('name', 'Delete Temp Directory')->first()->id;
+
+ $clone_repo_execution_step_id = ExecutionStep::where('name', 'Clone Repository')->first()->id;
+ $unzip_zip_files_execution_step_id = ExecutionStep::where('name', 'Unzip ZIP Files')->first()->id;
+ $checking_folder_structure_execution_step_id = ExecutionStep::where('name', 'Examine Folder Structure')->first()->id;
+ $add_env_file_execution_step_id = ExecutionStep::where('name', 'Add .env File')->first()->id;
+ $replace_package_json_execution_step_id = ExecutionStep::where('name', 'Replace package.json')->first()->id;
+ $copy_tests_folder_step_id = ExecutionStep::where('name', "Copy 'tests' Folder")->first()->id;
+ $npm_install_execution_step_id = ExecutionStep::where('name', 'NPM Install')->first()->id;
+ $npm_run_start_execution_step_id = ExecutionStep::where('name', 'NPM Run Start')->first()->id;
+ $npm_run_tests_execution_step_id = ExecutionStep::where('name', 'NPM Run Tests')->first()->id;
+ $delete_temp_directory_execution_step_id = ExecutionStep::where('name', 'Delete Temp Directory')->first()->id;
ProjectExecutionStep::insert([
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $clone_repo_execution_step_id,
'order' => 1,
'variables' => json_encode([
- '{{repoUrl}}', '{{tempDir}}',
+ '{{repoUrl}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $unzip_zip_files_execution_step_id,
'order' => 2,
'variables' => json_encode([
- '{{zipFileDir}}', '{{tempDir}}'
+ '{{zipFileDir}}',
+ '{{tempDir}}'
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $checking_folder_structure_execution_step_id,
'order' => 3,
'variables' => json_encode([
@@ -489,37 +252,52 @@ public function run(): void
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $add_env_file_execution_step_id,
'order' => 4,
'variables' => json_encode([
- '{{envFile}}', '{{tempDir}}',
+ '{{envFile}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $replace_package_json_execution_step_id,
'order' => 5,
'variables' => json_encode([
- '{{packageJson}}', '{{tempDir}}',
+ '{{packageJson}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $copy_tests_folder_step_id,
'order' => 6,
'variables' => json_encode([
- '{{testsDir}}', '{{tempDir}}',
+ // Format: {{sourceFile}}=media:filename:subfolder
+ // Contoh:
+ // '{{sourceFile}}=api:testfile.js:api' - places file in tests/api folder
+ // '{{sourceFile}}=web:homepage.test.js:web/integration' - places file in tests/web/integration folder
+ '{{sourceFile}}=tests:modul1-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul2-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul3-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul4-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul5-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul1-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul2-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul3-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul4-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul5-integration.test.js:integration',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $npm_install_execution_step_id,
'order' => 7,
'variables' => json_encode([
@@ -529,7 +307,7 @@ public function run(): void
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $npm_run_start_execution_step_id,
'order' => 8,
'variables' => null,
@@ -537,26 +315,26 @@ public function run(): void
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $npm_run_tests_execution_step_id,
'order' => 9,
'variables' => json_encode([
- '{{testFile}}=api-testA01',
- '{{testFile}}=web-testA01',
- '{{testFile}}=api-testA02',
- '{{testFile}}=web-testA02',
- '{{testFile}}=api-testA03',
- '{{testFile}}=web-testA03',
- '{{testFile}}=api-testA04',
- '{{testFile}}=web-testA04',
- '{{testFile}}=api-testA05',
- '{{testFile}}=web-testA05',
+ '{{testFile}}=modul1-unit.test.js',
+ '{{testFile}}=modul2-unit.test.js',
+ '{{testFile}}=modul3-unit.test.js',
+ '{{testFile}}=modul4-unit.test.js',
+ '{{testFile}}=modul5-unit.test.js',
+ '{{testFile}}=modul1-integration.test.js',
+ '{{testFile}}=modul2-integration.test.js',
+ '{{testFile}}=modul3-integration.test.js',
+ '{{testFile}}=modul4-integration.test.js',
+ '{{testFile}}=modul5-integration.test.js',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $delete_temp_directory_execution_step_id,
'order' => 10,
'variables' => json_encode([
@@ -565,113 +343,727 @@ public function run(): void
'created_at' => now(),
'updated_at' => now(),
],
+ ]);
+
+ $async_experiment_project_id = Project::where('title', 'asynchronous-programming')->first()->id;
+
+ ProjectsDefaultFileStructure::insert([
[
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $clone_repo_execution_step_id,
- 'order' => 1,
- 'variables' => json_encode([
- '{{repoUrl}}', '{{tempDir}}',
+ 'project_id' => $async_experiment_project_id,
+ 'structure' => json_encode([
+ 'src' => [
+ 'config' => [
+ 'database.js' => '',
+ ],
+ 'controllers' => [
+ 'mejaController.js' => '',
+ 'menuController.js' => '',
+ 'orderController.js' => '',
+ ],
+ 'models' => [
+ 'mejaModel.js' => '',
+ 'menuModel.js' => '',
+ 'orderModel.js' => '',
+ ],
+ 'middleware' => [
+ 'errorHandler.js' => '',
+ ],
+ 'routes' => [
+ 'mejaRoutes.js' => '',
+ 'menuRoutes.js' => '',
+ 'orderRoutes.js' => '',
+ ],
+ ],
+ 'node_modules' => '',
+ 'tests' => [],
+ '.env' => '',
+ '.env.example' => '',
+ '.gitignore' => '',
+ 'app.js' => '',
+ 'package-lock.json' => '',
+ 'package.json' => '',
+ 'README' => '',
+ 'server.js' => '',
]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $unzip_zip_files_execution_step_id,
- 'order' => 2,
- 'variables' => json_encode([
- '{{zipFileDir}}', '{{tempDir}}'
+ 'excluded' => json_encode([
+ 'node_modules',
+ 'tests',
+ '.env',
+ '.env.example',
+ '.gitignore',
+ 'package-lock.json',
+ 'README',
]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $checking_folder_structure_execution_step_id,
- 'order' => 3,
- 'variables' => json_encode([
- '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $add_env_file_execution_step_id,
- 'order' => 4,
- 'variables' => json_encode([
- '{{envFile}}', '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $replace_package_json_execution_step_id,
- 'order' => 5,
- 'variables' => json_encode([
- '{{packageJson}}', '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $copy_tests_folder_step_id,
- 'order' => 6,
- 'variables' => json_encode([
- '{{testsDir}}', '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $npm_install_execution_step_id,
- 'order' => 7,
- 'variables' => json_encode([
- '{{options}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $npm_run_start_execution_step_id,
- 'order' => 8,
- 'variables' => null,
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $npm_run_tests_execution_step_id,
- 'order' => 9,
- 'variables' => json_encode([
- '{{testFile}}=api-testB01',
- '{{testFile}}=web-testB01',
- '{{testFile}}=api-testB02',
- '{{testFile}}=web-testB02',
- '{{testFile}}=api-testB03',
- '{{testFile}}=web-testB03',
- '{{testFile}}=api-testB04',
- '{{testFile}}=web-testB04',
- '{{testFile}}=api-testB05',
- '{{testFile}}=web-testB05',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $delete_temp_directory_execution_step_id,
- 'order' => 10,
- 'variables' => json_encode([
- '{{tempDir}}',
+ 'replacements' => json_encode([
+ '.env',
+ 'tests',
+ 'package.json'
]),
'created_at' => now(),
'updated_at' => now(),
],
]);
}
+
+ /**
+ * Seed project default file structure
+ */
+// public function run(): void
+// {
+// // user
+// User::create([
+// 'name' => 'Omar',
+// 'email' => 'omar.yem1111@gmail.com',
+// 'password' => Hash::make('123456789'),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]);
+
+// // projects
+// Project::insert([[
+// 'title' => 'api-experiment',
+// 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
+// 'tech_stack' => json_encode([
+// 'framework' => 'ExpressJS',
+// 'language' => 'NodeJS',
+// 'database' => 'MongoDB',
+// 'testing' => 'Jest, Supertest, Puppeteer',
+// ]),
+// 'github_url' => 'https://github.com/Omar630603/api-experiment',
+// 'image' => 'image',
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ], [
+// 'title' => 'auth-experiment',
+// 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
+// 'tech_stack' => json_encode([
+// 'framework' => 'ExpressJS',
+// 'language' => 'NodeJS',
+// 'database' => 'MongoDB',
+// 'testing' => 'Jest, Supertest, Puppeteer',
+// ]),
+// 'github_url' => 'https://github.com/Omar630603/auth-experiment',
+// 'image' => 'image',
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]]);
+
+
+// $project_api_experiment = Project::where('title', 'api-experiment')->first();
+// $project_auth_experiment = Project::where('title', 'auth-experiment')->first();
+
+// // images
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/images/api-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/images/auth-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
+// //
+// // // files
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// //
+// // // tests
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/create-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/no-products-found-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/not-found-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/product-details-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/products-table-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/update-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-password-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page-after-register.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page-with-error.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/profile-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/register-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// //
+// // // guides
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// //
+// // // supplements
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/initial_data.json'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// //
+// // // zips
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+
+// $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
+// $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
+
+// // project default file structure
+// ProjectsDefaultFileStructure::insert([
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'structure' => json_encode([
+// 'controllers' => [
+// 'api' => [
+// 'product.controller.js' => '',
+// ],
+// 'web' => [
+// 'product.controller.js' => '',
+// ],
+// ],
+// 'models' => [
+// 'product.model.js' => '',
+// ],
+// 'node_modules' => '',
+// 'routes' => [
+// 'api' => [
+// 'product.routes.js' => '',
+// ],
+// 'web' => [
+// 'product.routes.js' => '',
+// ],
+// ],
+// 'tests' => [
+// 'api' => [
+// 'testA01.test.js' => '',
+// 'testA02.test.js' => '',
+// 'testA03.test.js' => '',
+// 'testA04.test.js' => '',
+// 'testA05.test.js' => '',
+// ],
+// 'web' => [
+// 'images' => [
+// 'create-product-page.png' => '',
+// 'error-notFound-page.png' => '',
+// 'index-page.png' => '',
+// 'no-products-found-page.png' => '',
+// 'not-found-product-page.png' => '',
+// 'product-details-page.png' => '',
+// 'products-table-page.png' => '',
+// 'update-product-page.png' => '',
+// ],
+// 'testA01.test.js' => '',
+// 'testA02.test.js' => '',
+// 'testA03.test.js' => '',
+// 'testA04.test.js' => '',
+// 'testA05.test.js' => '',
+// ],
+// ],
+// 'web' => [
+// 'layouts' => [
+// 'main.ejs' => '',
+// ],
+// 'styles' => [
+// 'main.css' => '',
+// ],
+// 'views' => [
+// 'products' => [
+// 'create.ejs' => '',
+// 'details.ejs' => '',
+// 'index.ejs' => '',
+// 'update.ejs' => '',
+// ],
+// 'error.ejs' => '',
+// 'index.ejs' => '',
+// ],
+// ],
+// '.env' => '',
+// '.env.example' => '',
+// '.gitignore' => '',
+// 'app.js' => '',
+// 'initial_data.json' => '',
+// 'package-lock.json' => '',
+// 'package.json' => '',
+// 'README' => '',
+// 'server.js' => '',
+// ]),
+// 'excluded' => json_encode([
+// 'node_modules',
+// 'tests',
+// '.env',
+// '.env.example',
+// '.gitignore',
+// 'package-lock.json',
+// 'initial_data.json',
+// 'README',
+// ]),
+// 'replacements' => json_encode([
+// '.env',
+// 'tests',
+// 'package.json'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'structure' => json_encode([
+// 'controllers' => [
+// 'api' => [
+// 'auth.controller.js' => '',
+// ],
+// 'web' => [
+// 'auth.controller.js' => '',
+// ],
+// ],
+// 'helpers' => [
+// 'errorhandler.helper.js' => '',
+// 'jsonwebtoken.helper.js' => '',
+// ],
+// 'models' => [
+// 'user.model.js' => '',
+// ],
+// 'node_modules' => '',
+// 'routes' => [
+// 'api' => [
+// 'auth.routes.js' => '',
+// ],
+// 'web' => [
+// 'auth.routes.js' => '',
+// ],
+// ],
+// 'services' => [
+// 'auth.service.js' => '',
+// ],
+// 'tests' => [
+// 'api' => [
+// 'testB01.test.js' => '',
+// 'testB02.test.js' => '',
+// 'testB03.test.js' => '',
+// 'testB04.test.js' => '',
+// 'testB05.test.js' => '',
+// ],
+// 'web' => [
+// 'images' => [
+// 'edit-page.png' => '',
+// 'edit-password-page.png' => '',
+// 'error-notFound-page.png' => '',
+// 'index-page.png' => '',
+// 'index-page-after-register.png' => '',
+// 'login-page.png' => '',
+// 'login-page-with-error.png' => '',
+// 'profile-page.png' => '',
+// 'register-page.png' => '',
+// ],
+// 'testB01.test.js' => '',
+// 'testB02.test.js' => '',
+// 'testB03.test.js' => '',
+// 'testB04.test.js' => '',
+// 'testB05.test.js' => '',
+// ],
+// ],
+// 'web' => [
+// 'layouts' => [
+// 'main.ejs' => '',
+// ],
+// 'styles' => [
+// 'main.css' => '',
+// ],
+// 'views' => [
+// 'auth' => [
+// 'edit.ejs' => '',
+// 'login.ejs' => '',
+// 'profile.ejs' => '',
+// 'register.ejs' => '',
+// ],
+// 'error.ejs' => '',
+// 'index.ejs' => '',
+// ],
+// ],
+// '.env' => '',
+// '.env.example' => '',
+// '.gitignore' => '',
+// 'app.js' => '',
+// 'package-lock.json' => '',
+// 'package.json' => '',
+// 'README' => '',
+// 'server.js' => '',
+// ]),
+// 'excluded' => json_encode([
+// 'node_modules',
+// 'tests',
+// '.env',
+// '.env.example',
+// '.gitignore',
+// 'package-lock.json',
+// 'README',
+// ]),
+// 'replacements' => json_encode([
+// '.env',
+// 'tests',
+// 'package.json'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]
+
+// ]);
+
+// // execution steps
+// ExecutionStep::insert([
+// [
+// 'name' => 'Clone Repository',
+// 'commands' => json_encode([
+// 'git', 'clone', '{{repoUrl}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Unzip ZIP Files',
+// 'commands' => json_encode([
+// 'unzip', '{{zipFileDir}}', '-d', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Remove ZIP Files',
+// 'commands' => json_encode([
+// 'rm', '-rf', '{{zipFileDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Examine Folder Structure',
+// 'commands' => json_encode([
+// 'ls', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Add .env File',
+// 'commands' => json_encode([
+// 'cp', '-r', '{{envFile}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Replace package.json',
+// 'commands' => json_encode([
+// 'cp', '-r', '{{packageJson}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => "Copy 'tests' Folder",
+// 'commands' => json_encode([
+// 'cp', '-r', '{{testsDir}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'NPM Install',
+// 'commands' => json_encode([
+// 'npm', 'install', '{{options}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'NPM Run Start',
+// 'commands' => json_encode([
+// 'npm', 'run', 'start',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'NPM Run Tests',
+// 'commands' => json_encode([
+// 'npm', 'run', '{{testFile}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Delete Temp Directory',
+// 'commands' => json_encode([
+// 'rm', '-rf', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]
+// ]);
+
+// // execution step projects
+// $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
+// $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
+
+// $clone_repo_execution_step_id = ExecutionStep::where('name', ExecutionStep::$CLONE_REPOSITORY)->first()->id;
+// $unzip_zip_files_execution_step_id = ExecutionStep::where('name', ExecutionStep::$UNZIP_ZIP_FILES)->first()->id;
+// $checking_folder_structure_execution_step_id = ExecutionStep::where('name', ExecutionStep::$EXAMINE_FOLDER_STRUCTURE)->first()->id;
+// $add_env_file_execution_step_id = ExecutionStep::where('name', ExecutionStep::$ADD_ENV_FILE)->first()->id;
+// $replace_package_json_execution_step_id = ExecutionStep::where('name', ExecutionStep::$REPLACE_PACKAGE_JSON)->first()->id;
+// $copy_tests_folder_step_id = ExecutionStep::where('name', ExecutionStep::$COPY_TESTS_FOLDER)->first()->id;
+// $npm_install_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_INSTALL)->first()->id;
+// $npm_run_start_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_START)->first()->id;
+// $npm_run_tests_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_TESTS)->first()->id;
+// $delete_temp_directory_execution_step_id = ExecutionStep::where('name', ExecutionStep::$DELETE_TEMP_DIRECTORY)->first()->id;
+
+// ProjectExecutionStep::insert([
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $clone_repo_execution_step_id,
+// 'order' => 1,
+// 'variables' => json_encode([
+// '{{repoUrl}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $unzip_zip_files_execution_step_id,
+// 'order' => 2,
+// 'variables' => json_encode([
+// '{{zipFileDir}}', '{{tempDir}}'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $checking_folder_structure_execution_step_id,
+// 'order' => 3,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $add_env_file_execution_step_id,
+// 'order' => 4,
+// 'variables' => json_encode([
+// '{{envFile}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $replace_package_json_execution_step_id,
+// 'order' => 5,
+// 'variables' => json_encode([
+// '{{packageJson}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $copy_tests_folder_step_id,
+// 'order' => 6,
+// 'variables' => json_encode([
+// '{{testsDir}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $npm_install_execution_step_id,
+// 'order' => 7,
+// 'variables' => json_encode([
+// '{{options}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $npm_run_start_execution_step_id,
+// 'order' => 8,
+// 'variables' => null,
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $npm_run_tests_execution_step_id,
+// 'order' => 9,
+// 'variables' => json_encode([
+// '{{testFile}}=api-testA01',
+// '{{testFile}}=web-testA01',
+// '{{testFile}}=api-testA02',
+// '{{testFile}}=web-testA02',
+// '{{testFile}}=api-testA03',
+// '{{testFile}}=web-testA03',
+// '{{testFile}}=api-testA04',
+// '{{testFile}}=web-testA04',
+// '{{testFile}}=api-testA05',
+// '{{testFile}}=web-testA05',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $delete_temp_directory_execution_step_id,
+// 'order' => 10,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $clone_repo_execution_step_id,
+// 'order' => 1,
+// 'variables' => json_encode([
+// '{{repoUrl}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $unzip_zip_files_execution_step_id,
+// 'order' => 2,
+// 'variables' => json_encode([
+// '{{zipFileDir}}', '{{tempDir}}'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $checking_folder_structure_execution_step_id,
+// 'order' => 3,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $add_env_file_execution_step_id,
+// 'order' => 4,
+// 'variables' => json_encode([
+// '{{envFile}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $replace_package_json_execution_step_id,
+// 'order' => 5,
+// 'variables' => json_encode([
+// '{{packageJson}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $copy_tests_folder_step_id,
+// 'order' => 6,
+// 'variables' => json_encode([
+// '{{testsDir}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $npm_install_execution_step_id,
+// 'order' => 7,
+// 'variables' => json_encode([
+// '{{options}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $npm_run_start_execution_step_id,
+// 'order' => 8,
+// 'variables' => null,
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $npm_run_tests_execution_step_id,
+// 'order' => 9,
+// 'variables' => json_encode([
+// '{{testFile}}=api-testB01',
+// '{{testFile}}=web-testB01',
+// '{{testFile}}=api-testB02',
+// '{{testFile}}=web-testB02',
+// '{{testFile}}=api-testB03',
+// '{{testFile}}=web-testB03',
+// '{{testFile}}=api-testB04',
+// '{{testFile}}=web-testB04',
+// '{{testFile}}=api-testB05',
+// '{{testFile}}=web-testB05',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $delete_temp_directory_execution_step_id,
+// 'order' => 10,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// ]);
+// }
}
diff --git a/package-lock.json b/package-lock.json
index 7f66dc3..bb0c951 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "iCLOP-V2",
+ "name": "iCLOP_V3",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/files/package.json b/public/assets/nodejs/projects/asycnchronous-programming/files/package.json
new file mode 100644
index 0000000..ae27766
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/files/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "restaurant-reservation",
+ "version": "1.0.0",
+ "description": "1. **Buat folder proyek** \r ```sh\r npm init -y",
+ "main": "app.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "nodemon server.js",
+ "praktikum1": "cross-env NODE_ENV=test jest -i test/praktikum1.test.js --testTimeout=20000",
+ "praktikum2": "cross-env NODE_ENV=test jest -i --verbose test/praktikum2Unit.test.js test/praktikum2Integration.test.js --testTimeout=20000",
+ "praktikum3": "cross-env NODE_ENV=test jest -i --verbose test/praktikum3Unit.test.js test/praktikum3Integration.test.js --testTimeout=20000"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "mongodb": "^6.13.1",
+ "mongoose": "^8.10.1"
+ },
+ "devDependencies": {
+ "cross-env": "^7.0.3",
+ "fs-extra": "^11.3.0",
+ "jest": "^29.7.0",
+ "nodemon": "^3.1.9",
+ "supertest": "^7.0.0"
+ }
+}
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP01.pdf b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP01.pdf
new file mode 100644
index 0000000..64171e4
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP01.pdf differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP02.pdf b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP02.pdf
new file mode 100644
index 0000000..bbf053f
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP02.pdf differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP03.pdf b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP03.pdf
new file mode 100644
index 0000000..dd7dd95
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP03.pdf differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP04.pdf b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP04.pdf
new file mode 100644
index 0000000..a269219
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP04.pdf differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP05.pdf b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP05.pdf
new file mode 100644
index 0000000..da0e63e
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP05.pdf differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/images/asycnchronous-programming.png b/public/assets/nodejs/projects/asycnchronous-programming/images/asycnchronous-programming.png
new file mode 100644
index 0000000..2d95b96
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/images/asycnchronous-programming.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/supplements/.env.example b/public/assets/nodejs/projects/asycnchronous-programming/supplements/.env.example
new file mode 100644
index 0000000..acc75f3
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/supplements/.env.example
@@ -0,0 +1,7 @@
+MONGODB_URI=mongodb+srv://
:@.incnimg.mongodb.net/?retryWrites=true&w=majority
+MONGODB_URI_TEST=mongodb+srv://:@.incnimg.mongodb.net/?retryWrites=true&w=majority
+PORT=pilih_port
+
+# ganti semua <> dengan kredensial Anda sendiri seperti yang diberikan oleh MongoDB Atlas (https://www.mongodb.com/cloud/atlas)
+# disarankan untuk menggunakan database yang berbeda untuk pengujian, oleh karena itu ada akhiran -test pada MONGODB_URI_TEST
+# pilih port yang belum digunakan (misalnya 5000)
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/supplements/.gitignore b/public/assets/nodejs/projects/asycnchronous-programming/supplements/.gitignore
new file mode 100644
index 0000000..37d7e73
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/supplements/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.env
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA01.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA01.test.js
new file mode 100644
index 0000000..545f9b8
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA01.test.js
@@ -0,0 +1,240 @@
+const mongoose = require("mongoose");
+const request = require("supertest");
+const app = require("../../app");
+const packages = require("../../package.json");
+
+require("dotenv").config();
+mongoose.set("strictQuery", true);
+
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+beforeAll(async () => {
+ await connectDB().then(
+ async () => {
+ console.log("Database connected successfully");
+ },
+ (err) => {
+ console.log("There is problem while connecting database " + err);
+ }
+ );
+});
+
+describe("Testing application configuration", () => {
+ // Testing the package.json file for the necessary development packages
+ // the packages are cross-env, jest, nodemon, supertest, jest-image-snapshot, jest-expect-message, puppeteer
+ it("Should have the necessary development packages", (done) => {
+ expect(
+ packages.devDependencies,
+ ).toHaveProperty("cross-env");
+ expect(
+ packages.devDependencies,
+ ).toHaveProperty("jest");
+ expect(
+ packages.devDependencies,
+ ).toHaveProperty("nodemon");
+ expect(
+ packages.devDependencies,
+ ).toHaveProperty("supertest");
+ // expect(
+ // packages.devDependencies,
+ // ).toHaveProperty("jest-image-snapshot");
+ // expect(
+ // packages.devDependencies,
+ // ).toHaveProperty("jest-expect-message");
+ // expect(
+ // packages.devDependencies,
+ // ).toHaveProperty("puppeteer");
+ done();
+ });
+ // Testing the package.json file for the necessary production packages
+ // the packages are dotenv, ejs, express, express-ejs-layouts, mongoose, mongoose-slug-generator
+ it("should have the necessary production packages", (done) => {
+ expect(
+ packages.dependencies,
+ `The package "dotenv" was not found in the dependencies object. Install the package by running this command "npm i dotenv --save"`,
+ options
+ ).toHaveProperty("dotenv");
+ expect(
+ packages.dependencies,
+ `The package "ejs" was not found in the dependencies object. Install the package by running this command "npm i ejs --save"`,
+ options
+ ).toHaveProperty("ejs");
+ expect(
+ packages.dependencies,
+ `The package "express" was not found in the dependencies object. Install the package by running this command "npm i express --save"`,
+ options
+ ).toHaveProperty("express");
+ expect(
+ packages.dependencies,
+ `The package "express-ejs-layouts" was not found in the dependencies object. Install the package by running this command "npm i express-ejs-layouts --save"`,
+ options
+ ).toHaveProperty("express-ejs-layouts");
+ expect(
+ packages.dependencies,
+ `The package "mongoose" was not found in the dependencies object. Install the package by running this command "npm i mongoose --save"`,
+ options
+ ).toHaveProperty("mongoose");
+ expect(
+ packages.dependencies,
+ `The package "mongoose-slug-generator" was not found in the dependencies object. Install the package by running this command "npm i mongoose-slug-generator --save"`,
+ options
+ ).toHaveProperty("mongoose-slug-generator");
+ done();
+ });
+ // Testing the application name
+ // the application name should be "api-experiment"
+ it("should have the right name and packages", (done) => {
+ expect(
+ packages.name,
+ `The name provided "${packages.name}" is wrong. The application name should be "api-experiment", check the package.json file`,
+ options
+ ).toBe("api-experiment");
+ done();
+ });
+ // Testing the application environment variables
+ // the application should have the following environment variables
+ // MONGODB_URI, MONGODB_URI_TEST, PORT
+ it("should have the right environment variables", (done) => {
+ expect(
+ process.env,
+ `The environment variable "MONGODB_URI" was not found. Check the .env file`,
+ options
+ ).toHaveProperty("MONGODB_URI");
+ expect(
+ process.env,
+ `The environment variable "MONGODB_URI_TEST" was not found. Check the .env.test file`,
+ options
+ ).toHaveProperty("MONGODB_URI_TEST");
+ expect(
+ process.env.MONGODB_URI !== process.env.MONGODB_URI_TEST,
+ `The environment variable "MONGODB_URI" and "MONGODB_URI_TEST" should not be the same. Check the .env`,
+ options
+ ).toBeTruthy();
+ expect(
+ process.env,
+ `The environment variable "PORT" was not found. Check the .env file`,
+ options
+ ).toHaveProperty("PORT");
+ expect(
+ process.env.NODE_ENV,
+ `The environment variable "NODE_ENV" was not found. Check the test script in the package.json file`
+ ).toBe("test");
+ done();
+ });
+ // Testing the application connection to the database using the test environment
+ it("should have the right database connection", (done) => {
+ expect(
+ mongoose.connection.readyState,
+ `The application is not connected to the database. Check the correctness of the MONGODB_URI_TEST variable in the .env file or the connection to the internet`,
+ options
+ ).toBe(1);
+ done();
+ });
+ // Testing the application configuration
+ it("should be using json format and express framework", (done) => {
+ let application_stack = [];
+ app._router.stack.forEach((element) => {
+ application_stack.push(element.name);
+ });
+ expect(
+ application_stack,
+ `The application is not using the json format. Check the app.js file`,
+ options
+ ).toContain("query");
+ expect(
+ application_stack,
+ `The application is not using the express framework. Check the app.js file`,
+ options
+ ).toContain("expressInit");
+ expect(
+ application_stack,
+ `The application is not using the json format. Check the app.js file`,
+ options
+ ).toContain("jsonParser");
+ expect(
+ application_stack,
+ `The application is not using the urlencoded format. Check the app.js file`,
+ options
+ ).toContain("urlencodedParser");
+ done();
+ });
+});
+// Testing the application testing route
+describe("Testing GET /api/v1/test", () => {
+ // Testing the application testing route without request
+ it("should return alive", async () => {
+ const res = await request(app).get("/api/v1/test");
+ expect(
+ res.statusCode,
+ `The status code should be 200, but it is "${res.statusCode}", change the status code in the function that handles the GET /api/v1/test route`,
+ options
+ ).toBe(200);
+ expect(
+ res.body,
+ `The response should contain the property "alive", but it does not, change the response in the function that handles the GET /api/v1/test route to return {alive: 'True'}`,
+ options
+ ).toHaveProperty("alive");
+ expect(
+ res.body.alive,
+ `The response should be {alive: 'True'}, but it is "${res.body.alive}", change the response in the function that handles the GET /api/v1/test route to return {alive: 'True'}`,
+ options
+ ).toBe("True");
+ expect(
+ res.req.method,
+ `The request method should be GET, but it is "${res.req.method}", change the request method in the function that handles the GET /api/v1/test route`,
+ options
+ ).toBe("GET");
+ expect(
+ res.type,
+ `The response type should be application/json, but it is "${res.type}", change the response type in the function that handles the GET /api/v1/test route`
+ ).toBe("application/json");
+ });
+ // Testing the application testing route with request
+ it("should return the same message", async () => {
+ const res = await request(app)
+ .get("/api/v1/test")
+ .send({ message: "Hello" });
+ expect(
+ res.statusCode,
+ `The status code should be 200, but it is "${res.statusCode}", change the status code in the function that handles the GET /api/v1/test route`,
+ options
+ ).toBe(200);
+ expect(
+ res.body,
+ `The response should contain the property "message", but it does not, change the response in the function that handles the GET /api/v1/test route to return {message: req.body.message}`,
+ options
+ ).toHaveProperty("message");
+ expect(
+ res.body.message,
+ `The response should be {message: 'Hello'}, but it is "${res.body.message}", change the response in the function that handles the GET /api/v1/test route to return {message: req.body.message}`,
+ options
+ ).toBe("Hello");
+ const res2 = await request(app)
+ .get("/api/v1/test")
+ .send({ message: "Hello World" });
+ expect(
+ res2.body.message,
+ `The response should be {message: 'Hello World'}, but it is "${res2.body.message}", change the response in the function that handles the GET /api/v1/test route to return {message: req.body.message}`,
+ options
+ ).toBe("Hello World");
+ });
+});
+
+afterAll(async () => {
+ await disconnectDB();
+});
+
+async function connectDB() {
+ return mongoose.connect(process.env.MONGODB_URI_TEST, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+}
+
+async function disconnectDB() {
+ await mongoose.connection.close();
+}
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA02.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA02.test.js
new file mode 100644
index 0000000..4c0ea1e
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA02.test.js
@@ -0,0 +1,306 @@
+const mongoose = require("mongoose");
+const request = require("supertest");
+const app = require("../../app");
+const Product = require("../../models/product.model");
+
+require("dotenv").config();
+mongoose.set("strictQuery", true);
+
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+beforeAll(async () => {
+ await connectDB(process.env.MONGODB_URI_TEST).then(
+ async () => {
+ console.log("Database connected successfully");
+ await createProducts();
+ },
+ (err) => {
+ console.log("There is problem while connecting database " + err);
+ }
+ );
+});
+
+describe("GET /api/v1/products", () => {
+ it("should return all products", async () => {
+ const res = await request(app).get("/api/v1/products");
+ expect(
+ res.statusCode,
+ `When calling GET /api/v1/products, the status code should be "200", but it was "${res.statusCode}", update your code to return 200`,
+ options
+ ).toBe(200);
+ expect(
+ res.body.message,
+ `When calling GET /api/v1/products, the message should be "Products found", but it was "${res.body.message}", update your code to return "Products found"`,
+ options
+ ).toBe("Products found");
+ expect(
+ res.body.products.length,
+ `When calling GET /api/v1/products, the products array should return more than 0 products, but it was "${res.body.products.length}", update your code to return more than 0 products`,
+ options
+ ).toBeGreaterThan(0);
+ expect(
+ res.body.products[0].name === "Product 1" ||
+ res.body.products[0].name === "Product 2",
+ `The data received from GET /api/v1/products was not correct, update your code to return the correct data`,
+ options
+ ).toBeTruthy();
+ expect(
+ res.req.method,
+ `When calling GET /api/v1/products, the method should be "GET", but it was "${res.req.method}", update the method to be "GET"`,
+ options
+ ).toBe("GET");
+ expect(
+ res.type,
+ `When calling GET /api/v1/products, the content type should be "application/json", but it was "${res.type}", update the content type to be "application/json"`,
+ options
+ ).toBe("application/json");
+ });
+
+ it("should check items in database", async () => {
+ await disconnectDB().then(async () => {
+ await connectDB(process.env.MONGODB_URI).then(async () => {
+ const products = await Product.find();
+ expect(
+ products.length,
+ `The database should contain all the "10 products" from the initial_data.json file, but it was "${products.length}", use the initial_data.json file to add more products to the api-experiment database`,
+ options
+ ).toEqual(10);
+ await disconnectDB().then(async () => {
+ await connectDB(process.env.MONGODB_URI_TEST);
+ });
+ });
+ });
+ });
+
+ it("should return no products", async () => {
+ await Product.deleteMany();
+ const res = await request(app).get("/api/v1/products");
+ expect(
+ res.statusCode,
+ `The status code should be "404" because there are no products, but it was "${res.statusCode}", update the status code to be 404 when there are no products`,
+ options
+ ).toBe(404);
+ expect(
+ res.body.message,
+ `The message should be "No products found" because there are no products, but it was "${res.body.message}", update the message to be "No products found" when there are no products`,
+ options
+ ).toBe("No products found");
+ await createProducts();
+ });
+
+ it("should return error 500 if the database disconnected", async () => {
+ await disconnectDB().then(async () => {
+ const res = await request(app).get("/api/v1/products");
+ expect(
+ res.statusCode,
+ `The application should return 500 for the status code if the database is disconnected`,
+ options
+ ).toBe(500);
+ await connectDB(process.env.MONGODB_URI_TEST);
+ });
+ });
+});
+
+describe("GET /api/v1/product/:slug", () => {
+ it("should return one product", async () => {
+ const res = await request(app).get("/api/v1/product/product-2");
+ expect(
+ res.statusCode,
+ `When calling GET /api/v1/product/:slug, the status code should be "200", but it was "${res.statusCode}", update your code to return 200`
+ ).toBe(200);
+ expect(
+ res.body.product.name,
+ `The expected product was not correct, update your code to return the correct product`,
+ options
+ ).toBe("Product 2");
+ expect(
+ res.req.method,
+ `When calling GET /api/v1/product/:slug, the method should be "GET", but it was "${res.req.method}", update the method to be "GET"`,
+ options
+ ).toBe("GET");
+ expect(
+ res.type,
+ `When calling GET /api/v1/product/:slug, the content type should be "application/json", but it was "${res.type}", update the content type to be "application/json"`,
+ options
+ ).toBe("application/json");
+ });
+
+ it("should return no product", async () => {
+ const res = await request(app).get("/api/v1/product/product-3");
+ expect(
+ res.statusCode,
+ `The status code should be "404" because there is no product, but it was "${res.statusCode}", update the status code to be 404 when there is no product found`,
+ options
+ ).toBe(404);
+ expect(
+ res.body.message,
+ `The message should be "No product found" because there is no product, but it was "${res.body.message}", update the message to be "No product found" when there is no product found`,
+ options
+ ).toBe("No product found");
+ });
+
+ it("should return error 500 if the database disconnected", async () => {
+ await disconnectDB().then(async () => {
+ const res = await request(app).get("/api/v1/product/product-2");
+ expect(
+ res.statusCode,
+ `The application should return 500 for the status code if the database is disconnected`,
+ options
+ ).toBe(500);
+ await connectDB(process.env.MONGODB_URI_TEST);
+ });
+ });
+});
+
+describe("GET /api/v1/products with filters", () => {
+ it("should not return any products", async () => {
+ const formData = {
+ search: "John Doe",
+ };
+ const res = await request(app).get("/api/v1/products").query(formData);
+ expect(
+ res.statusCode,
+ `When applying search filters, if there are no products matching the search query, the status code should be "404". but it was "${res.statusCode}", update your code to return "404" when there are no products matching the search query`,
+ options
+ ).toBe(404);
+ expect(
+ res.body.message,
+ `When applying search filters, if there are no products matching the search query, the message should be "No products found". but it was "${res.body.message}", update your code to return "No products found" when there are no products matching the search query`,
+ options
+ ).toBe("No products found");
+ });
+
+ it("should return one products that fits the minimum price", async () => {
+ const formData = {
+ price: {
+ minPrice: 200,
+ },
+ };
+ const res = await request(app).get("/api/v1/products").query(formData);
+ expect(
+ res.statusCode,
+ `When applying price filters, if there are products matching the price query, the status code should be "200". but it was "${res.statusCode}", update your code to return "200" when there are products matching the price query`,
+ options
+ ).toBe(200);
+ expect(
+ res.body.message,
+ `When applying price filters, if there are products matching the price query, the message should be "Products found". but it was "${res.body.message}", update your code to return "Products found" when there are products matching the price query`,
+ options
+ ).toBe("Products found");
+ expect(
+ res.body.products.length,
+ `When applying price filters, if there are products matching the price query, the length of the products should the same with the products that match the price query, update your code to return the correct amount of products`,
+ options
+ ).toBe(1);
+ expect(
+ res.body.products[0].price,
+ `When applying price filters, if there are products matching the price query, the price of the products should be greater than or equal to the minimum price, update your code to return the correct price of the products`,
+ options
+ ).toBeGreaterThanOrEqual(200);
+ });
+
+ it("should return two products that fits the maximum price", async () => {
+ const formData = {
+ price: {
+ maxPrice: 1000,
+ },
+ };
+ const res = await request(app).get("/api/v1/products").query(formData);
+ expect(
+ res.statusCode,
+ `When applying price filters, if there are products matching the price query, the status code should be "200". but it was "${res.statusCode}", update your code to return "200" when there are products matching the price query`,
+ options
+ ).toBe(200);
+ expect(
+ res.body.message,
+ `When applying price filters, if there are products matching the price query, the message should be "Products found". but it was "${res.body.message}", update your code to return "Products found" when there are products matching the price query`,
+ options
+ ).toBe("Products found");
+ expect(
+ res.body.products.length,
+ `When applying price filters, if there are products matching the price query, the length of the products should the same with the products that match the price query, update your code to return the correct amount of products`,
+ options
+ ).toBe(2);
+ expect(
+ res.body.products[0].price,
+ `When applying price filters, if there are products matching the price query, the price of the products should be less than or equal to the maximum price, update your code to return the correct price of the products`,
+ options
+ ).toBeLessThanOrEqual(1000);
+ });
+
+ it("should return products", async () => {
+ const formData = {
+ search: "Product",
+ price: {
+ minPrice: 200,
+ maxPrice: 1000,
+ },
+ };
+ const res = await request(app).get("/api/v1/products").query(formData);
+ expect(
+ res.statusCode,
+ `When applying search and price filters, if there are products matching the search and price query, the status code should be "200". but it was "${res.statusCode}", update your code to return "200" when there are products matching the search and price query`,
+ options
+ ).toBe(200);
+ expect(
+ res.body.message,
+ `When applying search and price filters, if there are products matching the search and price query, the message should be "Products found". but it was "${res.body.message}", update your code to return "Products found" when there are products matching the search and price query`,
+ options
+ ).toBe("Products found");
+ expect(
+ res.body.products.length,
+ `When applying search and price filters, if there are products matching the search and price query, the length of the products should the same with the products that match the search and price query, update your code to return the correct amount of products`,
+ options
+ ).toBe(1);
+ res.body.products.forEach((product) => {
+ expect(
+ product.price,
+ `When applying search and price filters, if there are products matching the search and price query, the price of the products should be greater than or equal to the minimum price and less than or equal to the maximum price, update your code to return the correct price of the products`,
+ options
+ ).toBeGreaterThanOrEqual(200);
+ expect(
+ product.price,
+ `When applying search and price filters, if there are products matching the search and price query, the price of the products should be greater than or equal to the minimum price and less than or equal to the maximum price, update your code to return the correct price of the products`,
+ options
+ ).toBeLessThanOrEqual(1000);
+ });
+ });
+});
+
+afterAll(async () => {
+ const collections = await mongoose.connection.db.collections();
+ for (let collection of collections) {
+ await collection.drop();
+ }
+ await disconnectDB();
+});
+
+async function createProducts() {
+ await Product.create(
+ {
+ name: "Product 1",
+ price: 100,
+ description: "Description 1",
+ },
+ {
+ name: "Product 2",
+ price: 200,
+ description: "Description 2",
+ }
+ );
+}
+
+async function connectDB(url) {
+ return mongoose.connect(url, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+}
+
+async function disconnectDB() {
+ await mongoose.connection.close();
+}
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA03.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA03.test.js
new file mode 100644
index 0000000..42ba37d
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA03.test.js
@@ -0,0 +1,243 @@
+const mongoose = require("mongoose");
+const request = require("supertest");
+const app = require("../../app");
+const Product = require("../../models/product.model");
+
+require("dotenv").config();
+mongoose.set("strictQuery", true);
+
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+beforeAll(async () => {
+ await connectDB(process.env.MONGODB_URI_TEST).then(
+ async () => {
+ console.log("Database connected successfully");
+ await createProducts();
+ },
+ (err) => {
+ console.log("There is problem while connecting database " + err);
+ }
+ );
+});
+
+describe("POST /api/v1/product", () => {
+ it("should create a product", async () => {
+ const res = await request(app).post("/api/v1/product").send({
+ name: "Product 3",
+ price: 1009,
+ description: "Description 3",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "201", but got "${res.statusCode}", the "201" is the status code for "Created" and it is the status code that we are expecting to get back from the server when we create a new product.`,
+ options
+ ).toBe(201);
+ expect(
+ res.body,
+ `Expected the response body to have a property called "message" and the value of that property should be "Product created"`,
+ options
+ ).toHaveProperty("message");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Product created"`,
+ options
+ ).toBe("Product created");
+
+ expect(
+ res.body,
+ `Expected the response body to have a property called "product" and the value of that property should be an object`,
+ options
+ ).toHaveProperty("product");
+ expect(
+ res.body.product.name,
+ `The value of the object returned doesn't match the value of the "name" property that we sent to the server.`,
+ options
+ ).toBe("Product 3");
+
+ expect(
+ res.req.method,
+ `Expected the request method to be "POST"`,
+ options
+ ).toBe("POST");
+ expect(
+ res.type,
+ `Expected the response type to be "application/json"`,
+ options
+ ).toBe("application/json");
+ });
+
+ it("should not create a product because it already exists", async () => {
+ const res = await request(app).post("/api/v1/product").send({
+ name: "Product 3",
+ price: 1009,
+ description: "Description 3",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "409", but got "${res.statusCode}", the "409" is the status code for "Conflict" and it is the status code that we are expecting to get back from the server when we try to create a product that already exists.`,
+ options
+ ).toBe(409);
+ expect(
+ res.body,
+ `Expected the response body to have a property called "message" and the value of that property should be "Product already exists"`,
+ options
+ ).toHaveProperty("message");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Product already exists"`,
+ options
+ ).toBe("Product already exists");
+ expect(
+ res.body,
+ `Expected the response body to have a property called "product" for the existed product and the value of that property should be an object`,
+ options
+ ).toHaveProperty("product");
+ expect(
+ res.body.product.name,
+ `The value of the object returned doesn't match the value of the "name" property that we sent to the server.`,
+ options
+ ).toBe("Product 3");
+ });
+
+ it("should not create a product because of the name is not provided", async () => {
+ const res = await request(app).post("/api/v1/product").send({
+ name: "",
+ price: 1009,
+ description: "Description 3",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without providing the name.`,
+ options
+ ).toBe(500);
+ expect(
+ res.body.errors.name.message,
+ `Expected the value of the "message" property to be "Name is required", but got "${res.body.errors.name.message}" instead. Change the validation property of the "name" property in the "product.model.js" file to "required: true" and then run the test again.`,
+ options
+ ).toBe("Name is required");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Product validation failed: name: Name is required", but got "${res.body.message}" instead. Change the validation property of the "name" property in the "product.model.js" file to "required: true" and then run the test again.`,
+ options
+ ).toContain("Name is required");
+ });
+
+ it("should not create a product because of the price is not provided", async () => {
+ const res = await request(app).post("/api/v1/product").send({
+ name: "Product 4",
+ price: "",
+ description: "Description 4",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without providing the price.`,
+ options
+ ).toBe(500);
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Cast to Number failed" for value "" at path "price" for model "Product", but got "${res.body.message}" instead. Change the validation property of the "price" property in the "product.model.js" file to "required: true" and then run the test again.`,
+ options
+ ).toContain("Cast to Number failed");
+ });
+
+ it("should not create a product because of the description is not provided", async () => {
+ const res = await request(app).post("/api/v1/product").send({
+ name: "Product 4",
+ price: 1009,
+ description: "",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without providing the description.`,
+ options
+ ).toBe(500);
+ expect(
+ res.body.errors.description.message,
+ `Expected the value of the "message" property to be "Description is required", but got "${res.body.errors.description.message}" instead. Change the validation property of the "description" property in the "product.model.js" file to "required: true" and then run the test again.`,
+ options
+ ).toBe("Description is required");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Product validation failed: description: Description is required", but got "${res.body.message}" instead. Change the validation property of the "description" property in the "product.model.js" file to "required: true" and then run the test again.`,
+ options
+ ).toContain("Description is required");
+ });
+
+ it("should not create a product because of the price is less than 0", async () => {
+ const res = await request(app).post("/api/v1/product").send({
+ name: "Product 4",
+ price: -1009,
+ description: "Description 4",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product with a price less than 0.
+ `,
+ options
+ ).toBe(500);
+ expect(
+ res.body.errors.price.message,
+ `Expected the value of the "message" property to be "Price must be greater than 0", but got "${res.body.errors.price.message}" instead. Change the validation property of the "price" property in the "product.model.js" file to "min: 0" and then run the test again.`,
+ options
+ ).toBe("Price must be greater than 0");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Price must be greater than 0", but got "${res.body.message}" instead. Change the validation property of the "price" property in the "product.model.js" file to "min: 0" and then run the test again.`,
+ options
+ ).toContain("Price must be greater than 0");
+ });
+
+ it("should return error 500", async () => {
+ await disconnectDB().then(async () => {
+ const res = await request(app).post("/api/v1/product").send({
+ name: "Product 4",
+ price: 1009,
+ description: "Description 4",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without database connection.`,
+ options
+ ).toBe(500);
+ await connectDB();
+ });
+ });
+});
+
+afterAll(async () => {
+ const collections = await mongoose.connection.db.collections();
+ for (let collection of collections) {
+ await collection.drop();
+ }
+ await disconnectDB();
+});
+
+async function createProducts() {
+ await Product.create(
+ {
+ name: "Product 1",
+ price: 100,
+ description: "Description 1",
+ },
+ {
+ name: "Product 2",
+ price: 200,
+ description: "Description 2",
+ }
+ );
+}
+
+async function connectDB() {
+ return mongoose.connect(process.env.MONGODB_URI_TEST, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+}
+
+async function disconnectDB() {
+ await mongoose.connection.close();
+}
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA04.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA04.test.js
new file mode 100644
index 0000000..6c45c5d
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA04.test.js
@@ -0,0 +1,153 @@
+const mongoose = require("mongoose");
+const request = require("supertest");
+const app = require("../../app");
+const Product = require("../../models/product.model");
+
+require("dotenv").config();
+mongoose.set("strictQuery", true);
+
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+beforeAll(async () => {
+ await connectDB(process.env.MONGODB_URI_TEST).then(
+ async () => {
+ console.log("Database connected successfully");
+ await createProducts();
+ },
+ (err) => {
+ console.log("There is problem while connecting database " + err);
+ }
+ );
+});
+
+describe("PATCH /api/v1/product/:slug", () => {
+ it("should update a product", async () => {
+ const FindProduct = await Product.findOne({ slug: "product-3" })
+ .lean()
+ .exec();
+ const res = await request(app).patch("/api/v1/product/product-3").send({
+ name: "Product 3 updated",
+ price: 109,
+ description: "Description 3 updated",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code 200, but got "${res.statusCode}", the status 200 means that the request has succeeded. Change it in the file "controllers/api/product.controller.js"`
+ ).toBe(200);
+ expect(
+ res.body,
+ `Expected the response body to have a property called "message" and the value of that property should be "Product updated"`,
+ options
+ ).toHaveProperty("message");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Product updated"`,
+ options
+ ).toBe("Product updated");
+ expect(
+ res.body,
+ `Expected the response body to have a property called "product" and the value of that property should be an object`,
+ options
+ ).toHaveProperty("product");
+
+ expect(
+ res.body.product,
+ `Expected the value of the product sent to the server to be updated but it is not. Make sure that you are using the "findByIdAndUpdate" method and that you are passing the correct parameters to it.`
+ ).not.toEqual(FindProduct);
+
+ expect(
+ res.req.method,
+ `Expected the request method to be "PATCH"`,
+ options
+ ).toBe("PATCH");
+ expect(
+ res.type,
+ `Expected the response content type to be "application/json"`,
+ options
+ ).toBe("application/json");
+ });
+
+ it("should not update a product because it does not exist", async () => {
+ const res = await request(app).patch("/api/v1/product/product_4").send({
+ name: "Product 3 updated",
+ price: 109,
+ description: "Description 3",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code 404, but got "${res.statusCode}", the status 404 means that the server can not find the requested resource. Change it in the file "controllers/api/product.controller.js"`,
+ options
+ ).toBe(404);
+
+ expect(
+ res.body,
+ `Expected the response body to have a property called "message" and the value of that property should be "No product found"`,
+ options
+ ).toHaveProperty("message");
+
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "No product found"`,
+ options
+ ).toBe("No product found");
+ });
+
+ it("should return error 500", async () => {
+ await disconnectDB().then(async () => {
+ const res = await request(app).patch("/api/v1/product/product_4").send({
+ name: "Product 3 updated",
+ price: 109,
+ description: "Description 3",
+ });
+ expect(
+ res.statusCode,
+ `Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to update a product without database connection.`,
+ options
+ ).toBe(500);
+ await connectDB();
+ });
+ });
+});
+
+afterAll(async () => {
+ const collections = await mongoose.connection.db.collections();
+ for (let collection of collections) {
+ await collection.drop();
+ }
+ await disconnectDB();
+});
+
+async function createProducts() {
+ await Product.create(
+ {
+ name: "Product 1",
+ price: 100,
+ description: "Description 1",
+ },
+ {
+ name: "Product 2",
+ price: 200,
+ description: "Description 2",
+ },
+ {
+ name: "Product 3",
+ price: 1009,
+ description: "Description 3",
+ }
+ );
+}
+
+async function connectDB() {
+ return mongoose.connect(process.env.MONGODB_URI_TEST, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+}
+
+async function disconnectDB() {
+ await mongoose.connection.close();
+}
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA05.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA05.test.js
new file mode 100644
index 0000000..6b40831
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/api/testA05.test.js
@@ -0,0 +1,141 @@
+const mongoose = require("mongoose");
+const request = require("supertest");
+const app = require("../../app");
+const Product = require("../../models/product.model");
+
+require("dotenv").config();
+mongoose.set("strictQuery", true);
+
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+beforeAll(async () => {
+ await connectDB(process.env.MONGODB_URI_TEST).then(
+ async () => {
+ console.log("Database connected successfully");
+ await createProducts();
+ },
+ (err) => {
+ console.log("There is problem while connecting database " + err);
+ }
+ );
+});
+
+describe("DELETE /api/v1/products/:slug", () => {
+ it("should delete a product", async () => {
+ const product = await Product.findOne({ slug: "product-3" }).lean().exec();
+ const res = await request(app).delete("/api/v1/product/product-3");
+ expect(
+ res.statusCode,
+ `Expected status code 200 when requesting to delete a product, but got "${res.statusCode}", the status 200 means that the request has succeeded. Change it in the file "controllers/api/product.controller.js"`
+ ).toBe(200);
+ expect(
+ res.body,
+ `Expected the response body to have a property called "message" and the value of that property should be "Product deleted"`,
+ options
+ ).toHaveProperty("message");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "Product deleted"`,
+ options
+ ).toBe("Product deleted");
+ expect(
+ res.body,
+ `Expected the response body to have a property called "product" for the deleted product and the value of that property should be an object`,
+ options
+ ).toHaveProperty("product");
+ expect(
+ res.body.product.name,
+ `Expected the product deleted to be the same as the product sent to the server but it is not. Make sure that you are using the "findByIdAndDelete" method and that you are passing the correct parameters to it.`,
+ options
+ ).toBe("Product 3");
+ const checkProduct = await Product.findById(product._id).lean().exec();
+ expect(
+ checkProduct,
+ `Expected the product to be deleted from the database but it is not. Make sure that you are using the "findByIdAndDelete" method and that you are passing the correct parameters to it.`,
+ options
+ ).toBeNull();
+ expect(
+ res.req.method,
+ `Expected the request method to be "DELETE" but it is not`,
+ options
+ ).toBe("DELETE");
+ expect(
+ res.type,
+ `Expected the response type to be "application/json" but it is not`,
+ options
+ ).toBe("application/json");
+ });
+
+ it("should not delete a product because it does not exist", async () => {
+ const res = await request(app).delete("/api/v1/product/product-3");
+ expect(
+ res.statusCode,
+ `Expected status code "404" when requesting to delete a product that does not exist, but got "${res.statusCode}", the status 404 means that the server can not find the requested resource. Change it in the file "controllers/api/product.controller.js"`
+ ).toBe(404);
+ expect(
+ res.body,
+ `Expected the response body to have a property called "message" and the value of that property should be "No product found"`,
+ options
+ ).toHaveProperty("message");
+ expect(
+ res.body.message,
+ `Expected the value of the "message" property to be "No product found"`,
+ options
+ ).toBe("No product found");
+ });
+
+ it("should return error 500", async () => {
+ await disconnectDB().then(async () => {
+ const res = await request(app).delete("/api/v1/product/product_4");
+ expect(
+ res.statusCode,
+ `Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to delete a product without database connection.`,
+ options
+ ).toBe(500);
+ await connectDB();
+ });
+ });
+});
+
+afterAll(async () => {
+ const collections = await mongoose.connection.db.collections();
+ for (let collection of collections) {
+ await collection.drop();
+ }
+ await disconnectDB();
+});
+
+async function createProducts() {
+ await Product.create(
+ {
+ name: "Product 1",
+ price: 100,
+ description: "Description 1",
+ },
+ {
+ name: "Product 2",
+ price: 200,
+ description: "Description 2",
+ },
+ {
+ name: "Product 3",
+ price: 1009,
+ description: "Description 3",
+ }
+ );
+}
+
+async function connectDB() {
+ return mongoose.connect(process.env.MONGODB_URI_TEST, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+}
+
+async function disconnectDB() {
+ await mongoose.connection.close();
+}
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/create-product-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/create-product-page.png
new file mode 100644
index 0000000..6ca3ad5
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/create-product-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/error-notFound-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/error-notFound-page.png
new file mode 100644
index 0000000..2167bff
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/error-notFound-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/index-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/index-page.png
new file mode 100644
index 0000000..d97fe07
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/index-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/no-products-found-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/no-products-found-page.png
new file mode 100644
index 0000000..46c7c63
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/no-products-found-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/not-found-product-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/not-found-product-page.png
new file mode 100644
index 0000000..bf2e82d
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/not-found-product-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/product-details-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/product-details-page.png
new file mode 100644
index 0000000..2464d24
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/product-details-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/products-table-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/products-table-page.png
new file mode 100644
index 0000000..8e0a7b6
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/products-table-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/update-product-page.png b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/update-product-page.png
new file mode 100644
index 0000000..f208026
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/images/update-product-page.png differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA01.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA01.test.js
new file mode 100644
index 0000000..2f295db
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA01.test.js
@@ -0,0 +1,209 @@
+const fs = require("fs");
+const puppeteer = require("puppeteer");
+const { toMatchImageSnapshot } = require("jest-image-snapshot");
+expect.extend({ toMatchImageSnapshot });
+
+require("dotenv").config();
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+let browser;
+let page;
+
+beforeAll(async () => {
+ browser = await puppeteer.launch({
+ headless: true,
+ slowMo: 0,
+ devtools: false,
+ defaultViewport: {
+ width: 1024,
+ height: 768,
+ },
+ });
+ page = await browser.newPage();
+ await page.setDefaultTimeout(10000);
+ await page.setDefaultNavigationTimeout(20000);
+});
+
+beforeEach(async () => {
+ await page.goto(`http://localhost:${process.env.PORT}/`);
+});
+
+afterAll(async () => {
+ await browser.close();
+});
+
+describe("Testing the index page title and content", () => {
+ it("should have the right title", async () => {
+ const title = await page.title();
+ expect(
+ title,
+ `The title for the web page "${title}" is wrong it should be "API-Experiment | Home" Make sure that the function handling the GET "/" route is sending the right title`,
+ options
+ ).toBe("API-Experiment | Home");
+ });
+
+ it("should have a button with the text 'Products' and url `/products` ", async () => {
+ const button = await page.$eval(
+ ".btn.btn-primary",
+ (el) => el.textContent
+ );
+ expect(
+ button,
+ `The button with the text "Products" is not present on the page`,
+ options
+ ).toBe("Products");
+
+ const url = await page.$eval(".btn.btn-primary", (el) => el.href);
+ expect(
+ url,
+ `The button with the text "Products" is not sending the user to the right url`,
+ options
+ ).toBe(`http://localhost:${process.env.PORT}/products`);
+
+ const backgroundColor = await page.evaluate(() => {
+ const button = document.querySelector(".btn.btn-primary");
+ const style = window.getComputedStyle(button);
+ return style.getPropertyValue("background-color");
+ });
+ expect(
+ backgroundColor,
+ `The button has the wrong background color "${backgroundColor}" it should be "rgb(0, 161, 189)"`
+ ).toBe("rgb(0, 161, 189)");
+ });
+
+ it("should have nav bar with 2 links", async () => {
+ const navBar = await page.$eval("nav", (el) => el.textContent);
+ expect(
+ navBar,
+ `The page should contain a link to the home page. Check the "main.ejs" file in the "web/views/layouts" folder to find the nav bar"`,
+ options
+ ).toContain("Home");
+ expect(
+ navBar,
+ `The page should contain a link to the products page. Check the "main.ejs" file in the "web/views/layouts" folder to find the nav bar`,
+ options
+ ).toContain("Products");
+ });
+});
+
+describe("Testing the index page for receiving messages", () => {
+ it("should receive a message and display it", async () => {
+ await page.goto(
+ `http://localhost:${process.env.PORT}/?message=Hello test`
+ );
+ let message = await page.$eval(".message", (el) => el.textContent);
+ expect(
+ message,
+ `the message "${message}" received is wrong it should be "Hello test"`,
+ options
+ ).toBe("Hello test");
+
+ await page.goto(
+ `http://localhost:${process.env.PORT}/?message=This is another test`
+ );
+ message = await page.$eval(".message", (el) => el.textContent);
+ expect(
+ message,
+ `the message "${message}" received is wrong it should be "This is another test"`,
+ options
+ ).toBe("This is another test");
+ });
+
+ it("should have the correct color for the box after receiving a message", async () => {
+ await page.goto(
+ `http://localhost:${process.env.PORT}/?message=yet, another test`
+ );
+ const backgroundColor = await page.evaluate(() => {
+ const message = document.querySelector(".alert.alert-success");
+ const style = window.getComputedStyle(message);
+ return style.getPropertyValue("background-color");
+ });
+ expect(
+ backgroundColor,
+ `The message box has the wrong background color "${backgroundColor}" it should be "rgb(239, 162, 95)"`
+ ).toBe("rgb(239, 162, 95)");
+ });
+});
+
+describe("Testing the error `Not Found` page", () => {
+ it("should have the right title", async () => {
+ await page.goto(
+ `http://localhost:${process.env.PORT}/thisurldoesnotexist`
+ );
+ const title = await page.title();
+ expect(
+ title,
+ `The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the GET "/:url" route is sending the right title`,
+ options
+ ).toBe("API-Experiment | Error");
+ });
+
+ it("should have a status code of 404", async () => {
+ await page.goto(
+ `http://localhost:${process.env.PORT}/thisurldoesnotexist`
+ );
+ const statusCode = await page.$eval(".title", (el) => el.textContent);
+ expect(
+ statusCode,
+ `The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the GET "/:url" route is sending the right status code`,
+ options
+ ).toBe("404");
+ });
+
+ it("should have a message saying `NOT FOUND`", async () => {
+ await page.goto(
+ `http://localhost:${process.env.PORT}/thisurldoesnotexist`
+ );
+ const message = await page.$eval(".message", (el) => el.textContent);
+ expect(
+ message,
+ `The message "${message}" is wrong it should be "NOT FOUND" Make sure that the function handling the GET "/:url" route is sending the right message`,
+ options
+ ).toBe("NOT FOUND");
+ });
+});
+
+describe("Testing the index page and error `Not Found` page image snapshots", () => {
+ it("matches the expected styling", async () => {
+ if (!fs.existsSync("tests/web/images/index-page.png")) {
+ throw new Error(
+ `The reference image for the index page does not exist, please import the image from the "tests/web/images/index-page.png"`
+ );
+ }
+ const screenshot = await page.screenshot({ fullPage: true });
+ expect(
+ screenshot,
+ `The web styling for the index page is not correct check the file "tests/web/images/__diff_output__/index-page-diff.png" to find the difference`,
+ options
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "index-page",
+ });
+ });
+
+ it("matches the expected styling", async () => {
+ if (!fs.existsSync("tests/web/images/error-notFound-page.png")) {
+ throw new Error(
+ `The reference image for the error page does not exist, please import the image from the "tests/web/images/error-notFound-page.png"`
+ );
+ }
+ await page.goto(
+ `http://localhost:${process.env.PORT}/thisurldoesnotexist`
+ );
+ const screenshot = await page.screenshot({ fullPage: true });
+ expect(
+ screenshot,
+ `The web styling for the error "Not Found" page is not correct check the file "tests/web/images/__diff_output__/error-notFound-page-diff.png" to find the difference`,
+ options
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "error-notFound-page",
+ });
+ });
+});
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA02.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA02.test.js
new file mode 100644
index 0000000..032e64e
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA02.test.js
@@ -0,0 +1,399 @@
+const fs = require("fs");
+const puppeteer = require("puppeteer");
+const { toMatchImageSnapshot } = require("jest-image-snapshot");
+const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
+expect.extend({ toMatchImageSnapshot });
+
+require("dotenv").config();
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+let browser;
+let page;
+
+beforeAll(async () => {
+ browser = await puppeteer.launch({
+ headless: true,
+ slowMo: 0,
+ devtools: false,
+ defaultViewport: {
+ width: 1024,
+ height: 768,
+ },
+ });
+ page = await browser.newPage();
+ await page.setDefaultTimeout(10000);
+ await page.setDefaultNavigationTimeout(20000);
+});
+
+beforeEach(async () => {
+ await page.goto(`http://localhost:${process.env.PORT}/products`);
+});
+
+afterAll(async () => {
+ await browser.close();
+});
+
+describe("Testing the products page title and content", () => {
+ it("should have the right title", async () => {
+ const title = await page.title();
+ expect(
+ title,
+ `The title for the web page "${title}" is wrong it should be "API-Experiment | Products" Make sure that the function handling the GET "/products" route is sending the right title`,
+ options
+ ).toBe("API-Experiment | Products");
+ });
+
+ it("should have a header with the text 'Products' with 'title' class", async () => {
+ const header = await page.$eval(".title", (el) => el.textContent);
+ expect(
+ header,
+ `The header with the text "Products" is not present on the page`,
+ options
+ ).toBe("Products");
+ });
+
+ it("should have the search form with the three inputs and the submit button", async () => {
+ const inputs = await page.$$("input");
+ expect(
+ inputs.length,
+ `The number of inputs in the search form is wrong, it should be 3`,
+ options
+ ).toBe(3);
+
+ const button = await page.$eval(
+ "form > button.btn.btn-primary",
+ (el) => el.textContent
+ );
+ expect(
+ button,
+ `The submit button is not present on the page`,
+ options
+ ).toBeTruthy();
+ expect(
+ button,
+ `The submit button should have the text "Search", but it has "${button}"`,
+ options
+ ).toBe("Search");
+ });
+
+ it("should have the correct form action and method", async () => {
+ const form = await page.$eval("form", (el) => ({
+ action: el.action,
+ method: el.method,
+ }));
+ expect(
+ form.action,
+ `The form action should be "http://localhost:${process.env.PORT}/products", but it has "${form.action}", You can change it in the "web/views/products/index.ejs" file.`,
+ options
+ ).toBe(`http://localhost:${process.env.PORT}/products`);
+ expect(
+ form.method,
+ `The form method should be "GET", but it has "${form.method}", You can change it in the "web/views/products/index.ejs" file.`,
+ options
+ ).toBe("get");
+ });
+
+ it("should the right inputs names and types", async () => {
+ const search_input = await page.$eval("#search", (el) => ({
+ name: el.name,
+ type: el.type,
+ }));
+ expect(
+ search_input.name,
+ `The first input should have the name "search", but it has "${search_input.name}"`,
+ options
+ ).toBe("search");
+ expect(
+ search_input.type,
+ `The first input should have the type "text", but it has "${search_input.type}"`,
+ options
+ ).toBe("text");
+
+ const minPrice_input = await page.$eval("#minPrice", (el) => ({
+ name: el.name,
+ type: el.type,
+ }));
+ expect(
+ minPrice_input.name,
+ `The second input should have the name "price[minPrice]", but it has "${minPrice_input.name}"`,
+ options
+ ).toBe("price[minPrice]");
+ expect(
+ minPrice_input.type,
+ `The second input should have the type "number", but it has "${minPrice_input.type}"`,
+ options
+ ).toBe("number");
+
+ const maxPrice_input = await page.$eval("#maxPrice", (el) => ({
+ name: el.name,
+ type: el.type,
+ }));
+ expect(
+ maxPrice_input.name,
+ `The third input should have the name "price[maxPrice]", but it has "${maxPrice_input.name}"`,
+ options
+ ).toBe("price[maxPrice]");
+ expect(
+ maxPrice_input.type,
+ `The third input should have the type "number", but it has "${maxPrice_input.type}"`,
+ options
+ ).toBe("number");
+ });
+
+ it("should have a button to create a new product", async () => {
+ const button = await page.$eval(".btn.btn-primary", (el) => ({
+ text: el.textContent,
+ url: el.href,
+ }));
+ expect(
+ button.text,
+ `The button to create a new product should have the text "Create a new product", but it has "${button.text}"`,
+ options
+ ).toBe("Create a new product");
+ expect(
+ button.url,
+ `The button to create a new product should have the url "http://localhost:${process.env.PORT}/products/create", but it has "${button.url}"`,
+ options
+ ).toBe(`http://localhost:${process.env.PORT}/products/create`);
+ });
+});
+
+describe("Testing the products page table", () => {
+ it("should have the right number of products", async () => {
+ const products = await page.$$("tbody tr");
+ expect(
+ products.length,
+ `The number of products is wrong, it should be 10`,
+ options
+ ).toBe(10);
+ });
+
+ it("should have the right data", async () => {
+ const products_data = await page.$$eval("tbody tr", (rows) =>
+ rows.map((row) => {
+ const [no, name, price, description, slug] = row.children;
+ return {
+ name: name.textContent,
+ price: parseFloat(price.textContent.replace("$", "")),
+ description: description.textContent,
+ slug: slug.children[0].href.split("/show/").pop(),
+ };
+ })
+ );
+
+ initial_data.forEach((product) => {
+ delete product._id;
+ delete product.createdAt;
+ delete product.updatedAt;
+ });
+
+ products_data.sort((a, b) => a.name.localeCompare(b.name));
+ initial_data.sort((a, b) => a.name.localeCompare(b.name));
+
+ expect(products_data, `The products data is wrong`, options).toEqual(
+ initial_data
+ );
+ });
+});
+
+describe("Testing the products details page", () => {
+ it("should go to the details page when clicking on a product", async () => {
+ await page.click("tbody tr:first-child a");
+ const url = await page.url();
+ const slug = url.split("/show/").pop();
+ const product = initial_data.find((product) => product.slug === slug);
+ expect(
+ product,
+ `The product with the slug "${slug}" is not present in the initial_data.json file`,
+ options
+ ).toBeTruthy();
+ expect(
+ url,
+ `The url for the details page is wrong, it should be "http://localhost:${process.env.PORT}/products/show/${product.slug}", but it is "${url}"`,
+ options
+ ).toBe(
+ `http://localhost:${process.env.PORT}/products/show/${product.slug}`
+ );
+ });
+
+ it("should have the button to edit and delete the product", async () => {
+ await page.click("tbody tr:first-child a");
+ const url = await page.url();
+ const slug = url.split("/show/").pop();
+ const product = initial_data.find((product) => product.slug === slug);
+ const productName = await page.$eval(
+ ".card-title",
+ (el) => el.textContent
+ );
+ const productPrice = await page.$eval(
+ ".card-subtitle",
+ (el) => el.textContent
+ );
+ const productDescription = await page.$eval(
+ ".card-text",
+ (el) => el.textContent
+ );
+ const editButton = await page.$eval("a.btn", (el) => ({
+ text: el.textContent.trim(),
+ url: el.href.trim(),
+ }));
+ const deleteButton = await page.$eval("form > button.btn", (el) => ({
+ text: el.textContent.trim(),
+ url: el.parentElement.action.trim(),
+ }));
+
+ expect(
+ productName,
+ `The product name is wrong, it should be "${product.name}", but it is "${productName}"`,
+ options
+ ).toBe(product.name);
+ expect(
+ productPrice,
+ `The product price is wrong, it should be "${product.price}", but it is "$${productPrice}"`,
+ options
+ ).toBe("$" + product.price);
+ expect(
+ productDescription,
+ `The product description is wrong, it should be "${product.description}", but it is "${productDescription}"`,
+ options
+ ).toBe(product.description);
+ expect(
+ editButton.text,
+ `The edit button should have the text "Edit this product", but it has "${editButton.text}", change it in the "views/products/details.ejs" file`,
+ options
+ ).toBe("Edit this product");
+ expect(
+ editButton.url,
+ `The edit button should have the url "http://localhost:${process.env.PORT}/products/update/${product.slug}", but it has "${editButton.url}", make sure you are using the correct slug by using "/products/update/<%= product.slug %>" url, change it in the "views/products/details.ejs" file`,
+ options
+ ).toBe(
+ `http://localhost:${process.env.PORT}/products/update/${product.slug}`
+ );
+ expect(
+ deleteButton.text,
+ `The delete button should have the text "Delete this product", but it has "${deleteButton.text}", change it in the "views/products/details.ejs" file`,
+ options
+ ).toBe("Delete this product");
+ expect(
+ deleteButton.url,
+ `The delete button should have the url "http://localhost:${process.env.PORT}/products/delete/${product.slug}", but it has "${deleteButton.url}", make sure you are using the correct slug by using "/products/delete/<%= product.slug %>" url, change it in the "views/products/details.ejs" file`,
+ options
+ ).toBe(
+ `http://localhost:${process.env.PORT}/products/delete/${product.slug}`
+ );
+ });
+
+ it("should don't go to the product's details page if the product does not exist", async () => {
+ await page.goto(
+ `http://localhost:${process.env.PORT}/products/show/123thisproductdoenotexist`
+ );
+
+ const title = await page.title();
+ expect(
+ title,
+ `The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the GET getProduct method return the error title if the product was not found`,
+ options
+ ).toBe("API-Experiment | Error");
+ const statusCode = await page.$eval(".title", (el) => el.textContent);
+ expect(
+ statusCode,
+ `The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the GET getProduct method return the error status code if the product was not found`,
+ options
+ ).toBe("404");
+ const message = await page.$eval(".message", (el) => el.textContent);
+ expect(
+ message,
+ `The message "${message}" is wrong it should be "No product found" Make sure that the function handling the GET getProduct method return the error message if the product was not found`,
+ options
+ ).toBe("No product found");
+ });
+});
+
+describe("Testing the product pages image snapshots", () => {
+ it("should have the right image for the products page", async () => {
+ if (!fs.existsSync("tests/web/images/products-table-page.png")) {
+ throw new Error(
+ `The reference image for the products table page does not exist, please import the image from the "tests/web/images/products-table-page.png"`
+ );
+ }
+
+ const image = await page.screenshot({ fullPage: true });
+ expect(
+ image,
+ `The image for the products table page is wrong, it should be the same as the "tests/web/images/__diff_output__/products-table-page-diff.png" image`,
+ options
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "products-table-page",
+ });
+ });
+
+ it("should have the right image for the details page", async () => {
+ if (!fs.existsSync("tests/web/images/product-details-page.png")) {
+ throw new Error(
+ `The reference image for the product details page does not exist, please import the image from the "tests/web/images/product-details-page.png"`
+ );
+ }
+
+ await page.goto(
+ `http://localhost:${process.env.PORT}/products/show/${initial_data[0].slug}`
+ );
+ const image = await page.screenshot({ fullPage: true });
+ expect(
+ image,
+ `The image for the product details page is wrong, it should be the same as the "tests/web/images/__diff_output__/product-details-page-diff.png" image`,
+ options
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "product-details-page",
+ });
+ });
+
+ it("should match the not found product snapshot", async () => {
+ if (!fs.existsSync("tests/web/images/not-found-product-page.png")) {
+ throw new Error(
+ `The reference image for the not found product page does not exist, please import the image from the "tests/web/images/not-found-product-page.png"`
+ );
+ }
+
+ await page.goto(
+ `http://localhost:${process.env.PORT}/products/show/123`
+ );
+ const image = await page.screenshot({ fullPage: true });
+ expect(
+ image,
+ `The image for the not found product page is wrong, it should be the same as the "tests/web/images/__diff_output__/not-found-product-page-diff.png" image`
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "not-found-product-page",
+ });
+ });
+
+ it("should match no products found snapshot", async () => {
+ if (!fs.existsSync("tests/web/images/no-products-found-page.png")) {
+ throw new Error(
+ `The reference image for the no products found page does not exist, please import the image from the "tests/web/images/no-products-found-page.png"`
+ );
+ }
+
+ await page.goto(
+ `http://localhost:${process.env.PORT}/products?search=123ThisIsNotAProduct`
+ );
+ const image = await page.screenshot({ fullPage: true });
+ expect(
+ image,
+ `The image for the no products found page is wrong, it should be the same as the "tests/web/images/__diff_output__/no-products-found-page-diff.png" image`
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "no-products-found-page",
+ });
+ });
+});
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA03.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA03.test.js
new file mode 100644
index 0000000..52ea83a
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA03.test.js
@@ -0,0 +1,246 @@
+const fs = require("fs");
+const puppeteer = require("puppeteer");
+const { toMatchImageSnapshot } = require("jest-image-snapshot");
+const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
+const mongoose = require("mongoose");
+expect.extend({ toMatchImageSnapshot });
+
+require("dotenv").config();
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+let browser;
+let page;
+
+beforeAll(async () => {
+ browser = await puppeteer.launch({
+ headless: true,
+ slowMo: 0,
+ devtools: false,
+ defaultViewport: {
+ width: 1024,
+ height: 768,
+ },
+ });
+ page = await browser.newPage();
+ await page.setDefaultTimeout(10000);
+ await page.setDefaultNavigationTimeout(20000);
+});
+
+beforeEach(async () => {
+ await page.goto(`http://localhost:${process.env.PORT}/products/create`);
+ mongoose.set("strictQuery", true);
+ await mongoose.connect(process.env.MONGODB_URI, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+ await mongoose.connection.collection("products").deleteMany({});
+ initial_data.forEach((product) => {
+ delete product._id;
+ delete product.createdAt;
+ delete product.updatedAt;
+ });
+ await mongoose.connection.collection("products").insertMany(initial_data);
+ await mongoose.connection.close();
+});
+
+afterAll(async () => {
+ await browser.close();
+});
+
+describe("Testing the create product page title and content", () => {
+ it("should have the correct title", async () => {
+ const title = await page.title();
+ expect(
+ title,
+ `The title received "${title}" of the page is not correct, it should be "API-Experiment | Create Product". Change the title of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
+ options
+ ).toBe("API-Experiment | Create Product");
+ });
+
+ it("should have the correct content title and description", async () => {
+ const title = await page.$eval(".title", (el) => el.textContent);
+ const description = await page.$eval(
+ ".description",
+ (el) => el.textContent
+ );
+
+ expect(
+ title,
+ `The title received "${title}" of the page's header is not correct, it should be "Create a new product". Change the title of the page to match the expected one. You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("Create a new product");
+
+ expect(
+ description,
+ `The description received "${description}" of the page's header is not correct, it should be "Fill the form below to create a new product". Change the description of the page to match the expected one. You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("Fill the form below to create a new product");
+ });
+});
+
+describe("Testing the create product page form", () => {
+ it("should have the correct form fields", async () => {
+ const inputs = await page.$$("input");
+ expect(
+ inputs.length,
+ `The number of inputs in the create form is wrong, it should be 2`,
+ options
+ ).toBe(2);
+
+ const textarea = await page.$$("textarea");
+ expect(
+ textarea.length,
+ `The number of textarea in the create form is wrong, it should be 1`,
+ options
+ ).toBe(1);
+
+ const button = await page.$eval(
+ "form > button.btn.btn-primary",
+ (el) => el.textContent
+ );
+ expect(
+ button,
+ `The submit button is not present on the page, You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBeTruthy();
+ expect(
+ button,
+ `The submit button should have the text "Create", but it has "${button}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("Create");
+ });
+
+ it("should the right inputs names and types", async () => {
+ const name_input = await page.$eval("#name", (el) => ({
+ name: el.name,
+ type: el.type,
+ }));
+ expect(
+ name_input.name,
+ `The name input should have the name "name", but it has "${name_input.name}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("name");
+ expect(
+ name_input.type,
+ `The name input should have the type "text", but it has "${name_input.type}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("text");
+
+ const price_input = await page.$eval("#price", (el) => ({
+ name: el.name,
+ type: el.type,
+ }));
+ expect(
+ price_input.name,
+ `The price input should have the name "price", but it has "${price_input.name}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("price");
+ expect(
+ price_input.type,
+ `The price input should have the type "number", but it has "${price_input.type}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("number");
+
+ const description_input = await page.$eval("#description", (el) => ({
+ name: el.name,
+ rows: el.rows,
+ }));
+ expect(
+ description_input.name,
+ `The description textarea should have the name "description", but it has "${description_input.name}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("description");
+ expect(
+ description_input.rows,
+ `The description textarea should have "5" rows "number", but it has "${description_input.rows}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe(5);
+ });
+
+ it("should have the correct form action and method", async () => {
+ const form = await page.$eval("form", (el) => ({
+ action: el.action,
+ method: el.method,
+ }));
+ expect(
+ form.action,
+ `The form action should be "http://localhost:${process.env.PORT}/products/create", but it has "${form.action}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe(`http://localhost:${process.env.PORT}/products/create`);
+ expect(
+ form.method,
+ `The form method should be "POST", but it has "${form.method}", You can change it in the "web/views/products/create.ejs" file.`,
+ options
+ ).toBe("post");
+ });
+});
+
+describe("Testing the create product page form submission", () => {
+ it("should create a new product", async () => {
+ await page.type("#name", "Test Product");
+ await page.type("#price", "100");
+ await page.type("#description", "Test Product Description");
+ await page.click("form > button.btn.btn-primary");
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ const message = await page.$eval("p.message", (el) => el.textContent);
+ expect(
+ message,
+ `The message received "${message}" of the page is not correct, it should be "Product created". Change the message of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
+ options
+ ).toBe("Product created");
+
+ const newProduct = await page.$eval(
+ "table > tbody > tr:last-child",
+ (el) => ({
+ name: el.children[1].textContent,
+ price: el.children[2].textContent,
+ description: el.children[3].textContent,
+ })
+ );
+ expect(
+ newProduct,
+ `The test product created seems to be not in the table of products, make sure that the product after being created is added to the table of products. You can change it in the "controllers/web/product.controller.js" file.`
+ ).toEqual({
+ name: "Test Product",
+ price: "$100",
+ description: "Test Product Description",
+ });
+ });
+
+ it("should not create a new product with empty fields", async () => {
+ await page.type("#name", "New Product");
+ await page.click("form > button.btn.btn-primary");
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ const message = await page.$eval("p.message", (el) => el.textContent);
+ expect(
+ message,
+ `The message received "${message}" of the page is not correct, it should be "Please fill all fields". Change the message of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
+ options
+ ).toBe("Please fill all fields");
+ });
+});
+
+describe("Testing the create page image snapshot", () => {
+ it("should match the reference image", async () => {
+ if (!fs.existsSync("tests/web/images/create-product-page.png")) {
+ throw new Error(
+ `The reference image for the create product page does not exist, please import the image from the "tests/web/images/create-product-page.png"`
+ );
+ }
+ const image = await page.screenshot({ fullPage: true });
+ expect(
+ image,
+ `The image for the create product page is wrong, it should be the same as the "tests/web/images/__diff_output__/create-product-page-diff.png" image`
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "create-product-page",
+ });
+ });
+});
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA04.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA04.test.js
new file mode 100644
index 0000000..3e5405a
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA04.test.js
@@ -0,0 +1,342 @@
+const fs = require("fs");
+const puppeteer = require("puppeteer");
+const { toMatchImageSnapshot } = require("jest-image-snapshot");
+const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
+const mongoose = require("mongoose");
+expect.extend({ toMatchImageSnapshot });
+
+require("dotenv").config();
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+let browser;
+let page;
+let product;
+
+beforeAll(async () => {
+ browser = await puppeteer.launch({
+ headless: true,
+ slowMo: 0,
+ devtools: false,
+ defaultViewport: {
+ width: 1024,
+ height: 768,
+ },
+ });
+ page = await browser.newPage();
+ await page.setDefaultTimeout(10000);
+ await page.setDefaultNavigationTimeout(20000);
+});
+
+beforeEach(async () => {
+ mongoose.set("strictQuery", true);
+ await mongoose.connect(process.env.MONGODB_URI, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+ await mongoose.connection.collection("products").deleteMany({});
+ initial_data.forEach((product) => {
+ delete product._id;
+ delete product.createdAt;
+ delete product.updatedAt;
+ });
+ await mongoose.connection.collection("products").insertMany(initial_data);
+ await mongoose.connection.close();
+
+ await page.goto(`http://localhost:${process.env.PORT}/products`);
+ await page.click("tbody tr:first-child a");
+ const url = await page.url();
+ const slug = url.split("/show/").pop();
+ product = initial_data.find((product) => product.slug === slug);
+
+ await page.goto(
+ `http://localhost:${process.env.PORT}/products/update/${slug}`
+ );
+});
+
+afterAll(async () => {
+ await browser.close();
+});
+
+describe("Testing the update page title and content", () => {
+ it("should have the correct title", async () => {
+ const title = await page.title();
+ expect(
+ title,
+ `The title received "${title}" of the page is not correct, it should be "API-Experiment | Update Product". Change the title of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
+ options
+ ).toBe("API-Experiment | Update Product");
+ });
+ it("should have the correct content title and description", async () => {
+ const title = await page.$eval(".title", (el) => el.textContent);
+ const description = await page.$eval(
+ ".description",
+ (el) => el.textContent
+ );
+
+ expect(
+ title,
+ `The title received "${title}" of the page's header is not correct, it should be "Update this product". Change the title of the page to match the expected one. You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("Update this product");
+
+ expect(
+ description,
+ `The description received "${description}" of the page's header is not correct, it should be "Fill the form below to update this product". Change the description of the page to match the expected one. You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("Fill the form below to update this product");
+ });
+});
+
+describe("Testing the create product page form", () => {
+ it("should have the correct form fields", async () => {
+ const inputs = await page.$$("input");
+ expect(
+ inputs.length,
+ `The number of inputs in the create form is wrong, it should be 2`,
+ options
+ ).toBe(2);
+
+ const textarea = await page.$$("textarea");
+ expect(
+ textarea.length,
+ `The number of textarea in the create form is wrong, it should be 1`,
+ options
+ ).toBe(1);
+
+ const button = await page.$eval(
+ "form > button.btn.btn-primary",
+ (el) => el.textContent
+ );
+ expect(
+ button,
+ `The submit button is not present on the page, You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBeTruthy();
+ expect(
+ button,
+ `The submit button should have the text "Update", but it has "${button}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("Update");
+ });
+
+ it("should the right inputs names and types", async () => {
+ const name_input = await page.$eval("#name", (el) => ({
+ name: el.name,
+ type: el.type,
+ value: el.value,
+ }));
+ expect(
+ name_input.name,
+ `The name input should have the name "name", but it has "${name_input.name}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("name");
+ expect(
+ name_input.type,
+ `The name input should have the type "text", but it has "${name_input.type}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("text");
+ expect(
+ name_input.value,
+ `The name input should have the value "${product.name}", but it has "${name_input.value}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe(product.name);
+
+ const price_input = await page.$eval("#price", (el) => ({
+ name: el.name,
+ type: el.type,
+ value: el.value,
+ }));
+ expect(
+ price_input.name,
+ `The price input should have the name "price", but it has "${price_input.name}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("price");
+ expect(
+ price_input.type,
+ `The price input should have the type "number", but it has "${price_input.type}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("number");
+ expect(
+ price_input.value,
+ `The price input should have the value "${product.price}", but it has "${price_input.value}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe(product.price.toString());
+
+ const description_input = await page.$eval("#description", (el) => ({
+ name: el.name,
+ rows: el.rows,
+ value: el.textContent.trim(),
+ }));
+ expect(
+ description_input.name,
+ `The description textarea should have the name "description", but it has "${description_input.name}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("description");
+ expect(
+ description_input.rows,
+ `The description textarea should have "5" rows "number", but it has "${description_input.rows}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe(5);
+ expect(
+ description_input.value,
+ `The description textarea should have the value "${product.description}", but it has "${description_input.value}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe(product.description);
+ });
+
+ it("should have the correct form action and method", async () => {
+ const form = await page.$eval("form", (el) => ({
+ action: el.action,
+ method: el.method,
+ }));
+ expect(
+ form.action,
+ `The form action should be "http://localhost:${process.env.PORT}/products/update/${product.slug}", but it has "${form.action}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe(
+ `http://localhost:${process.env.PORT}/products/update/${product.slug}`
+ );
+ expect(
+ form.method,
+ `The form method should be "POST", but it has "${form.method}", You can change it in the "web/views/products/update.ejs" file.`,
+ options
+ ).toBe("post");
+ });
+});
+
+describe("Testing the create product page form submission", () => {
+ it("should update the product", async () => {
+ let nameInput = await page.$("#name");
+ let priceInput = await page.$("#price");
+ let descriptionInput = await page.$("#description");
+ await nameInput.click({ clickCount: 3 });
+ await nameInput.press("Backspace");
+ await priceInput.click({ clickCount: 3 });
+ await priceInput.press("Backspace");
+ await descriptionInput.click({ clickCount: 3 });
+ await descriptionInput.press("Backspace");
+ await nameInput.type("Updated product");
+ await priceInput.type("99.99");
+ await descriptionInput.type("Updated description");
+ await page.click("form > button.btn.btn-primary");
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ const url = await page.url();
+ expect(
+ url,
+ `The page url should be "http://localhost:${process.env.PORT}/products/update/${product.slug}", but it has "${url}", You can change it in the "controllers/web/products.controller.js" file.`,
+ options
+ ).toBe(
+ `http://localhost:${process.env.PORT}/products/update/${product.slug}`
+ );
+ const message = await page.$eval("p.message", (el) => el.textContent);
+ expect(
+ message,
+ `The message should be "Product updated", but it has "${message}", You can change it in the "controllers/web/products.controller.js" file.`,
+ options
+ ).toBe("Product updated");
+
+ const productName = await page.$eval(
+ ".card-title",
+ (el) => el.textContent
+ );
+ const productPrice = await page.$eval(
+ ".card-subtitle",
+ (el) => el.textContent
+ );
+ const productDescription = await page.$eval(
+ ".card-text",
+ (el) => el.textContent
+ );
+
+ expect(
+ productName,
+ `The product after updated should have the new updated name, but it has "${productName}", You can change the update method in the "controllers/web/products.controller.js" file.`,
+ options
+ ).toBe("Updated product");
+ expect(
+ productPrice,
+ `The product after updated should have the new updated price, but it has "${productPrice}", You can change the update method in the "controllers/web/products.controller.js" file.`,
+ options
+ ).toBe("$99");
+ expect(
+ productDescription,
+ `The product after updated should have the new updated description, but it has "${productDescription}", You can change the update method in the "controllers/web/products.controller.js" file.`,
+ options
+ ).toBe("Updated description");
+ });
+
+ it("should not update the product if the name is empty", async () => {
+ let nameInput = await page.$("#name");
+ await nameInput.click({ clickCount: 3 });
+ await nameInput.press("Backspace");
+ await page.click("form > button.btn.btn-primary");
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ const message = await page.$eval("p.message", (el) => el.textContent);
+ expect(
+ message,
+ `The message received "${message}" of the page is not correct, it should be "Please fill all fields". Change the message of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
+ options
+ ).toBe("Please fill all fields");
+ });
+
+ it("should don't update the product if the product does not exist", async () => {
+ await page.goto(
+ `http://localhost:${process.env.PORT}/products/update/123thisproductdoenotexist`
+ );
+
+ const title = await page.title();
+ expect(
+ title,
+ `The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the GET updateProduct method return the error title if the product was not found`,
+ options
+ ).toBe("API-Experiment | Error");
+ const statusCode = await page.$eval(".title", (el) => el.textContent);
+ expect(
+ statusCode,
+ `The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the GET updateProduct method return the error status code if the product was not found`,
+ options
+ ).toBe("404");
+ const message = await page.$eval(".message", (el) => el.textContent);
+ expect(
+ message,
+ `The message "${message}" is wrong it should be "No product found" Make sure that the function handling the GET updateProduct method return the error message if the product was not found`,
+ options
+ ).toBe("No product found");
+ });
+});
+
+describe("Testing the update product page image snapshot", () => {
+ it("should match the update product page image snapshot", async () => {
+ let nameInput = await page.$("#name");
+ let priceInput = await page.$("#price");
+ let descriptionInput = await page.$("#description");
+ await nameInput.click({ clickCount: 3 });
+ await nameInput.press("Backspace");
+ await priceInput.click({ clickCount: 3 });
+ await priceInput.press("Backspace");
+ await descriptionInput.click({ clickCount: 3 });
+ await descriptionInput.press("Backspace");
+
+ if (!fs.existsSync("tests/web/images/update-product-page.png")) {
+ throw new Error(
+ `The reference image for the update product page does not exist, please import the image from the "tests/web/images/update-product-page.png"`
+ );
+ }
+
+ const image = await page.screenshot({ fullPage: true });
+ expect(
+ image,
+ `The image for the update product page is wrong, it should be the same as the "tests/web/images/__diff_output__/update-product-page-diff.png" image`
+ ).toMatchImageSnapshot({
+ customDiffConfig: { threshold: 0.9 },
+ customSnapshotsDir: "tests/web/images",
+ customSnapshotIdentifier: "update-product-page",
+ });
+ });
+});
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA05.test.js b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA05.test.js
new file mode 100644
index 0000000..9bb2708
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/tests/web/testA05.test.js
@@ -0,0 +1,141 @@
+const fs = require("fs");
+const puppeteer = require("puppeteer");
+const { toMatchImageSnapshot } = require("jest-image-snapshot");
+const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
+const mongoose = require("mongoose");
+expect.extend({ toMatchImageSnapshot });
+
+require("dotenv").config();
+const options = {
+ showPrefix: false,
+ showMatcherMessage: true,
+ showStack: true,
+};
+
+let browser;
+let page;
+let product;
+
+beforeAll(async () => {
+ browser = await puppeteer.launch({
+ headless: true,
+ slowMo: 0,
+ devtools: false,
+ defaultViewport: {
+ width: 1024,
+ height: 768,
+ },
+ });
+ page = await browser.newPage();
+ await page.setDefaultTimeout(10000);
+ await page.setDefaultNavigationTimeout(20000);
+});
+
+beforeEach(async () => {
+ mongoose.set("strictQuery", true);
+ await mongoose.connect(process.env.MONGODB_URI, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+ await mongoose.connection.collection("products").deleteMany({});
+ initial_data.forEach((product) => {
+ delete product._id;
+ delete product.createdAt;
+ delete product.updatedAt;
+ });
+ await mongoose.connection.collection("products").insertMany(initial_data);
+ await mongoose.connection.close();
+
+ await page.goto(`http://localhost:${process.env.PORT}/products`);
+ await page.click("tbody tr:first-child a");
+ const url = await page.url();
+ const slug = url.split("/show/").pop();
+ product = initial_data.find((product) => product.slug === slug);
+});
+
+afterAll(async () => {
+ await browser.close();
+});
+
+describe("Testing the delete form in the details page", () => {
+ it("should delete a product", async () => {
+ await page.click("form > button.btn");
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ const url = await page.url();
+ expect(
+ url,
+ `The url for deleting "${url}" a product is not correct, it should be "http://localhost:${process.env.PORT}/products/delete/${product.slug}"`,
+ options
+ ).toBe(
+ `http://localhost:${process.env.PORT}/products/delete/${product.slug}`
+ );
+
+ const message = await page.$eval("p.message", (el) => el.textContent);
+ expect(
+ message,
+ `The message for deleting "${message}" a product is not correct, it should be "Product deleted"`,
+ options
+ ).toBe("Product deleted");
+
+ await page.goto(`http://localhost:${process.env.PORT}/products`);
+
+ const products_data = await page.$$eval("tbody tr", (rows) =>
+ rows.map((row) => {
+ const [no, name, price, description, slug] = row.children;
+ return {
+ name: name.textContent,
+ price: parseFloat(price.textContent.replace("$", "")),
+ description: description.textContent,
+ slug: slug.children[0].href.split("/show/").pop(),
+ };
+ })
+ );
+ initial_data.forEach((product) => {
+ delete product._id;
+ delete product.createdAt;
+ delete product.updatedAt;
+ });
+
+ products_data.sort((a, b) => a.name.localeCompare(b.name));
+ initial_data.sort((a, b) => a.name.localeCompare(b.name));
+
+ expect(
+ products_data,
+ `The deleted product should not be in the list of products`,
+ options
+ ).not.toContainEqual(product);
+ });
+
+ it("should don't delete the product if the product does not exist", async () => {
+ await page.setRequestInterception(true);
+ page.on("request", (interceptedRequest) => {
+ var data = {
+ method: "POST",
+ };
+ interceptedRequest.continue(data);
+ });
+
+ await page.goto(
+ `http://localhost:${process.env.PORT}/products/delete/1234567890`
+ );
+
+ const title = await page.title();
+ expect(
+ title,
+ `The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the POST deleteProduct method return the error title if the product was not found`,
+ options
+ ).toBe("API-Experiment | Error");
+ const statusCode = await page.$eval(".title", (el) => el.textContent);
+ expect(
+ statusCode,
+ `The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the POST deleteProduct method return the error status code if the product was not found`,
+ options
+ ).toBe("404");
+ const message = await page.$eval(".message", (el) => el.textContent);
+ expect(
+ message,
+ `The message "${message}" is wrong it should be "No product found" Make sure that the function handling the POST deleteProduct method return the error message if the product was not found`,
+ options
+ ).toBe("No product found");
+ });
+});
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/zips/guides.zip b/public/assets/nodejs/projects/asycnchronous-programming/zips/guides.zip
new file mode 100644
index 0000000..9481730
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/zips/guides.zip differ
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/zips/supplements.zip b/public/assets/nodejs/projects/asycnchronous-programming/zips/supplements.zip
new file mode 100644
index 0000000..3c4fd76
Binary files /dev/null and b/public/assets/nodejs/projects/asycnchronous-programming/zips/supplements.zip differ
diff --git a/resources/views/nodejs/dashboard/partials/projects/card.blade.php b/resources/views/nodejs/dashboard/partials/projects/card.blade.php
index 9a6d606..72cfbb7 100644
--- a/resources/views/nodejs/dashboard/partials/projects/card.blade.php
+++ b/resources/views/nodejs/dashboard/partials/projects/card.blade.php
@@ -1,6 +1,6 @@
}})
+ onerror="this.onerror=null;this.src='{{ asset('assets/nodejs/placeholder.png') }}';">
{{$project->title}}
diff --git a/resources/views/nodejs/profile/edit.blade.php b/resources/views/nodejs/profile/edit.blade.php
index ef69910..4419845 100644
--- a/resources/views/nodejs/profile/edit.blade.php
+++ b/resources/views/nodejs/profile/edit.blade.php
@@ -9,19 +9,19 @@
- @include('profile.partials.update-profile-information-form')
+ @include('nodejs.profile.partials.update-profile-information-form')
- @include('profile.partials.update-password-form')
+ @include('nodejs.profile.partials.update-password-form')
- @include('profile.partials.delete-user-form')
+ @include('nodejs.profile.partials.delete-user-form')
diff --git a/resources/views/nodejs/projects/partials/card.blade.php b/resources/views/nodejs/projects/partials/card.blade.php
index 9a6d606..72cfbb7 100644
--- a/resources/views/nodejs/projects/partials/card.blade.php
+++ b/resources/views/nodejs/projects/partials/card.blade.php
@@ -1,6 +1,6 @@
}})
+ onerror="this.onerror=null;this.src='{{ asset('assets/nodejs/placeholder.png') }}';">
{{$project->title}}
diff --git a/resources/views/nodejs/projects/partials/details.blade.php b/resources/views/nodejs/projects/partials/details.blade.php
index 857ac60..98f8c60 100644
--- a/resources/views/nodejs/projects/partials/details.blade.php
+++ b/resources/views/nodejs/projects/partials/details.blade.php
@@ -26,7 +26,7 @@ class="inline-block bg-gray-200 dark:bg-gray-700 rounded-full px-3 py-1 text-sm
}})
+ onerror="this.onerror=null;this.src='{{ asset('assets/nodejs/placeholder.png') }}';">
\ No newline at end of file
diff --git a/resources/views/nodejs/submissions/change_source_code.blade.php b/resources/views/nodejs/submissions/change_source_code.blade.php
index 1ffe4c1..040de31 100644
--- a/resources/views/nodejs/submissions/change_source_code.blade.php
+++ b/resources/views/nodejs/submissions/change_source_code.blade.php
@@ -20,8 +20,7 @@
+ :value="old('github_url')" placeholder="E.g. https://github.com/username/repository.git" />
@@ -53,16 +52,16 @@
url: url,
process: {
headers: {
- 'X-CSRF-TOKEN': '{{ csrf_token()}}'
+ 'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
},
},
allowMultiple: false,
- acceptedFileTypes: ['application/x-zip-compressed'],
+ acceptedFileTypes: ['application/x-zip-compressed', 'application/zip'],
fileValidateTypeDetectType: (source, type) =>
new Promise((resolve, reject) => {
- resolve(type);
- }),
+ resolve(type);
+ }),
});
pond.on('addfile', function() {
if (pond.getFiles().length > 0) {
@@ -77,16 +76,16 @@
url: url,
process: {
headers: {
- 'X-CSRF-TOKEN': '{{ csrf_token()}}'
+ 'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
},
},
allowMultiple: false,
- acceptedFileTypes: ['application/x-zip-compressed'],
+ acceptedFileTypes: ['application/x-zip-compressed', 'application/zip'],
fileValidateTypeDetectType: (source, type) =>
new Promise((resolve, reject) => {
- resolve(type);
- }),
+ resolve(type);
+ }),
});
});
@@ -103,16 +102,16 @@
url: url,
process: {
headers: {
- 'X-CSRF-TOKEN': '{{ csrf_token()}}'
+ 'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
},
},
allowMultiple: false,
- acceptedFileTypes: ['application/x-zip-compressed'],
+ acceptedFileTypes: ['application/x-zip-compressed', 'application/zip'],
fileValidateTypeDetectType: (source, type) =>
new Promise((resolve, reject) => {
- resolve(type);
- }),
+ resolve(type);
+ }),
});
});
@@ -120,8 +119,10 @@
$('.filepond--credits').hide();
$('.filepond--panel-root').addClass('bg-gray-900 ');
- $('.filepond--drop-label').addClass('border-2 border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-secondary-500 dark:focus:border-secondary-600 focus:ring-secondary-500 dark:focus:ring-secondary-600 rounded-md shadow-sm ');
-
+ $('.filepond--drop-label').addClass(
+ 'border-2 border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-secondary-500 dark:focus:border-secondary-600 focus:ring-secondary-500 dark:focus:ring-secondary-600 rounded-md shadow-sm '
+ );
+
$('form').on('submit', function(e) {
e.preventDefault();
if (pond.getFiles().length > 0) {
@@ -149,7 +150,8 @@
button: "Ok",
}).then(function() {
const submission_id = data.submission.id;
- window.location = "/nodejs/submissions/submission/" + submission_id;
+ window.location = "/nodejs/submissions/submission/" +
+ submission_id;
});
},
error: function(data) {
@@ -203,7 +205,8 @@
button: "Ok",
}).then(function() {
const submission_id = data.submission.id;
- window.location = "/nodejs/submissions/submission/" + submission_id;
+ window.location = "/nodejs/submissions/submission/" +
+ submission_id;
});
},
error: function(data) {
@@ -217,9 +220,9 @@
}
});
}
- });
+ });
}
}
});
-
\ No newline at end of file
+
diff --git a/resources/views/nodejs/submissions/show.blade.php b/resources/views/nodejs/submissions/show.blade.php
index 7246202..4eec0b8 100644
--- a/resources/views/nodejs/submissions/show.blade.php
+++ b/resources/views/nodejs/submissions/show.blade.php
@@ -1,701 +1,826 @@
- @if(request()->routeIs('submissions.showAll'))
-
-
- {{ __('All submissions for project: ') . $project->title }}
-
-
+ @if (request()->routeIs('submissions.showAll'))
+
+
+ {{ __('All submissions for project: ') . $project->title }}
+
+
-
-
-
- @php
- $no = 0;
- @endphp
- @forelse ($submissions as $submission)
- @php
- $no += 1;
- @endphp
-
-
-
-
- {{$submission->project->title}} - submission number #{{$no}}
- Here is the the
- list of attempts for this submission
- Total
- Attempts: {{$submission->getTotalAttemptsCount()}}
-
-
-
- | Attempt NO# |
- Status |
- Time Spent |
- Description |
- Edit |
-
-
-
-
- |
-
- {{$submission->attempts}}
-
- |
-
- @php
- $statusClass = '';
- if ($submission->status === 'completed') {
- $statusClass = 'bg-green-100 text-green-800';
- } elseif ($submission->status === 'failed') {
- $statusClass = 'bg-red-100 text-red-800';
- } elseif ($submission->status === 'processing') {
- $statusClass = 'bg-secondary-100 text-secondary-800';
- } elseif ($submission->status === 'pending') {
- $statusClass = 'bg-blue-100 text-blue-800';
- }
- @endphp
-
- {{ucfirst($submission->status)}}
-
- |
-
- {{-- get the difference between start and end time from submission using carbon and if end is null then use current time --}}
- @php
- $start = Carbon\Carbon::parse($submission->start);
- $end = Carbon\Carbon::parse($submission->end ?? now());
- $time = $end->diff($start)->format('%H:%I:%S');
- @endphp
- {{$time}}
- |
-
-
- Current Attempt
-
- |
-
- View
- @if ($submission->status === 'completed' || $submission->status === 'failed')
- |
- Download
- Results
- @endif
- |
-
-
- |
- Past Attempts
- |
-
- @forelse ($submission->history->sortByDesc('attempts') as $history)
-
- |
- {{$history->attempts}}
- |
-
- @php
- $statusClass = '';
- if ($history->status === 'completed') {
- $statusClass = 'bg-green-100 text-green-800';
- } elseif ($history->status === 'failed') {
- $statusClass = 'bg-red-100 text-red-800';
- } elseif ($history->status === 'processing') {
- $statusClass = 'bg-secondary-100 text-secondary-800';
- } elseif ($history->status === 'pending') {
- $statusClass = 'bg-blue-100 text-blue-800';
- }
- @endphp
-
- {{ucfirst($history->status)}}
-
- |
-
- @php
- $history_start = Carbon\Carbon::parse($history->start);
- $history_end = Carbon\Carbon::parse($history->end ?? now());
- $history_time = $history_end->diff($history_start)->format('%H:%I:%S');
- @endphp
- {{$history_time}}
- |
-
- {{$history->description}}
- |
-
- View
- |
- Download
- Results
- |
-
- @empty
-
-
-
- @endforelse
-
-
-
- @empty
-
-
-
+
+
+
+ @php
+ $no = 0;
+ @endphp
+ @forelse ($submissions as $submission)
+ @php
+ $no += 1;
+ @endphp
+
+
+
+
+ {{ $submission->project->title }} - submission number #{{ $no }}
+ Here is the
+ the
+ list of attempts for this submission
+
+ Total
+ Attempts: {{ $submission->getTotalAttemptsCount() }}
+
+
+
+ | Attempt NO# |
+ Status |
+ Time Spent |
+ Description |
+ Edit |
+
+
+
+
+ |
+
+ {{ $submission->attempts }}
+
+ |
+
+ @php
+ $statusClass = '';
+ if ($submission->status === 'completed') {
+ $statusClass = 'bg-green-100 text-green-800';
+ } elseif ($submission->status === 'failed') {
+ $statusClass = 'bg-red-100 text-red-800';
+ } elseif ($submission->status === 'processing') {
+ $statusClass = 'bg-secondary-100 text-secondary-800';
+ } elseif ($submission->status === 'pending') {
+ $statusClass = 'bg-blue-100 text-blue-800';
+ }
+ @endphp
+
+ {{ ucfirst($submission->status) }}
+
+ |
+
+ {{-- get the difference between start and end time from submission using carbon and if end is null then use current time --}}
+ @php
+ $start = Carbon\Carbon::parse($submission->start);
+ $end = Carbon\Carbon::parse($submission->end ?? now());
+ $time = $end->diff($start)->format('%H:%I:%S');
+ @endphp
+ {{ $time }}
+ |
+
+
+ Current Attempt
+
+ |
+
+ View
+ @if ($submission->status === 'completed' || $submission->status === 'failed')
+ |
+ Download
+ Results
+ @endif
+ |
+
+
+ |
+ Past Attempts
+ |
+
+ @forelse ($submission->history->sortByDesc('attempts') as $history)
+
+ |
+ {{ $history->attempts }}
+ |
+
+ @php
+ $statusClass = '';
+ if ($history->status === 'completed') {
+ $statusClass = 'bg-green-100 text-green-800';
+ } elseif ($history->status === 'failed') {
+ $statusClass = 'bg-red-100 text-red-800';
+ } elseif ($history->status === 'processing') {
+ $statusClass = 'bg-secondary-100 text-secondary-800';
+ } elseif ($history->status === 'pending') {
+ $statusClass = 'bg-blue-100 text-blue-800';
+ }
+ @endphp
+
+ {{ ucfirst($history->status) }}
+
+ |
+
+ @php
+ $history_start = Carbon\Carbon::parse($history->start);
+ $history_end = Carbon\Carbon::parse($history->end ?? now());
+ $history_time = $history_end
+ ->diff($history_start)
+ ->format('%H:%I:%S');
+ @endphp
+ {{ $history_time }}
+ |
+
+ {{ $history->description }}
+ |
+
+ View
+ |
+ Download
+ Results
+ |
+
+ @empty
+
+
+
+ @endforelse
+
+
+
+ @empty
+
+
+
@endforelse
-
+
@elseif(request()->routeIs('submissions.show') || request()->routeIs('submissions.history'))
-
-
- {{ __('Submission NO#').$submission->id.__(' of Project: ') . $submission->project->title .__(' attempt NO#: ').$submission->attempts }}
-
-
+
+
+ {{ __('Submission NO#') . $submission->id . __(' of Project: ') . $submission->project->title . __(' attempt NO#: ') . $submission->attempts }}
+
+
-
-
-
-
-
-
-
- -
-
-
-
-
- Start
-
-
- @forelse ($steps as $step)
- -
-
-
-
-
- {{$step->executionStep->name}}
-
-
- @if ($step->executionStep->name == 'NPM Run Tests')
- @forelse ($step->variables as $testCommandValue)
- @php
- $command = implode(" ",$step->executionStep->commands);
- $key = explode("=",$testCommandValue)[0];
- $value = explode("=",$testCommandValue)[1];
- $testStep = str_replace($key, $value, $command);
- $iconID = str_replace(" ", "_", $testStep);
- @endphp
- -
-
-
-
-
- {{$testStep}}
-
-
- @empty
-
- @endforelse
- @endif
- @empty
-
- @endforelse
- -
-
-
-
-
- Done
-
-
-
+
+
+
+
+
+
+
+ -
+
+
+
+
+ Start
+
+
+ @forelse ($steps as $step)
+ -
+
+
+
+
+ {{ $step->executionStep->name }}
+
+
+ @if ($step->executionStep->name == 'NPM Run Tests')
+ @forelse ($step->variables as $testCommandValue)
+ @php
+ $command = implode(' ', $step->executionStep->commands);
+ $key = explode('=', $testCommandValue)[0];
+ $value = explode('=', $testCommandValue)[1];
+ $testStep = str_replace($key, $value, $command);
+ $iconID = str_replace(' ', '_', $testStep);
+ $iconID = str_replace('.', '_', $iconID);
+ @endphp
+ -
+
+
+
+
+ {{ $testStep }}
+
+
+ @empty
+
+ @endforelse
+ @endif
+ @empty
+
+ @endforelse
+ -
+
+
+
+
+ Done
+
+
+
+
-
-
-
+
+
-
-
- Loading...
-
-
-
-
-
-
-
- Waitting for the submission to progress...
+
+
+ Loading...
+
+
+
+
+
+
+
+ Waitting for the submission to progress...
+
-
- @section('scripts')
-
- @endsection
+ }
+ });
+
+ @endsection
@endif
-
\ No newline at end of file
+