HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/tests/Feature/LanguageUploadFeatureTest.php
<?php

/**
 * Language::upload() — safe per-file ZIP upload flow.
 *
 * Covers:
 *   - ZIP missing a registered file         → restored from master
 *   - ZIP with extra unregistered files     → ignored, no errors
 *   - ZIP with a PHP-corrupt file           → error reported, existing file kept
 *   - ZIP with extra keys / missing keys    → locked to master, keys dropped/restored
 *   - Non-ZIP upload                        → validator fails with a clear message
 */

use App\Model\Language;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;

uses(Tests\TestCase::class);

/**
 * Subclass that lets each test control the target dir + registered files,
 * without touching the real Hook registry or resource_path('lang/...').
 */
class TestableLanguage extends Language
{
    public $__testDir = null;
    public $__testFiles = [];

    public function languageDir()
    {
        return $this->__testDir . '/lang-zz';
    }

    public function getAllLanguageFiles()
    {
        return $this->__testFiles;
    }
}

function makeZip(string $path, array $entries): void
{
    $zip = new ZipArchive();
    $zip->open($path, ZipArchive::CREATE);
    foreach ($entries as $name => $content) {
        $zip->addFromString($name, $content);
    }
    $zip->close();
}

function makeTestLanguage(string $testDir): TestableLanguage
{
    $lang = new TestableLanguage();
    $lang->code = 'zz';
    $lang->__testDir = $testDir;
    $lang->__testFiles = [
        'messages' => [
            'id' => 'messages',
            'type' => 'default',
            'path' => $testDir . '/lang-zz/messages.php',
            'file_title' => 'messages.php',
            'master_translation_file' => $testDir . '/master/messages.php',
        ],
        'validation' => [
            'id' => 'validation',
            'type' => 'default',
            'path' => $testDir . '/lang-zz/validation.php',
            'file_title' => 'validation.php',
            'master_translation_file' => $testDir . '/master/validation.php',
        ],
    ];

    return $lang;
}

function makeUploadRequest(string $zipPath): Request
{
    $file = new UploadedFile(
        $zipPath,
        'package.zip',
        'application/zip',
        null,
        true // test mode — bypass is_uploaded_file() check
    );

    $request = Request::create('/upload', 'POST');
    $request->files->set('file', $file);

    return $request;
}

beforeEach(function () {
    $this->testDir = storage_path('tmp/lang-upload-test-' . uniqid());
    File::makeDirectory($this->testDir, 0755, true);
    File::makeDirectory($this->testDir . '/lang-zz', 0755, true);
    File::makeDirectory($this->testDir . '/master', 0755, true);
    File::makeDirectory($this->testDir . '/zips', 0755, true);

    // Master (authoritative) files
    File::put(
        $this->testDir . '/master/messages.php',
        "<?php return array('hello' => 'Hello', 'bye' => 'Goodbye') ?>"
    );
    File::put(
        $this->testDir . '/master/validation.php',
        "<?php return array('required' => 'Field is required') ?>"
    );

    // Existing target translations (what's currently in lang/zz/)
    File::put(
        $this->testDir . '/lang-zz/messages.php',
        "<?php return array('hello' => 'Hola', 'bye' => 'Adios') ?>"
    );
    File::put(
        $this->testDir . '/lang-zz/validation.php',
        "<?php return array('required' => 'Campo requerido') ?>"
    );
});

afterEach(function () {
    if (isset($this->testDir) && is_dir($this->testDir)) {
        File::deleteDirectory($this->testDir);
    }
});

test('upload: ZIP missing a registered file restores it from master', function () {
    $zipPath = $this->testDir . '/zips/missing.zip';
    makeZip($zipPath, [
        // only messages.php — validation.php intentionally missing
        'messages.php' => "<?php return array('hello' => 'Salut', 'bye' => 'Au revoir') ?>",
    ]);

    $lang = makeTestLanguage($this->testDir);
    $validator = $lang->upload(makeUploadRequest($zipPath));

    expect($validator->fails())->toBeFalse();

    // messages.php — values updated from ZIP
    $messages = include $this->testDir . '/lang-zz/messages.php';
    expect($messages['hello'])->toBe('Salut');
    expect($messages['bye'])->toBe('Au revoir');

    // validation.php — missing from ZIP, restored from master
    $validation = include $this->testDir . '/lang-zz/validation.php';
    expect($validation['required'])->toBe('Field is required');

    // Report
    $report = $lang->uploadReport;
    expect($report['summary']['total'])->toBe(2);
    expect($report['summary']['restored_missing'])->toBe(1);
    expect($report['summary']['errors'])->toBe(0);

    $byId = collect($report['files'])->keyBy('id');
    expect($byId['messages']['status'])->toBe('ok');
    expect($byId['validation']['status'])->toBe('restored_missing');
});

test('upload: extra unregistered files in ZIP are ignored without error', function () {
    $zipPath = $this->testDir . '/zips/extra.zip';
    makeZip($zipPath, [
        'messages.php'   => "<?php return array('hello' => 'Hi', 'bye' => 'Bye') ?>",
        'validation.php' => "<?php return array('required' => 'Required') ?>",
        // extra files not in the registered list — should be ignored silently
        'unregistered.php' => "<?php return array('x' => 'y') ?>",
        'readme.txt'       => "this is not a translation file",
        'subdir/other.php' => "<?php return array('foo' => 'bar') ?>",
    ]);

    $lang = makeTestLanguage($this->testDir);
    $validator = $lang->upload(makeUploadRequest($zipPath));

    expect($validator->fails())->toBeFalse();

    // Both registered files updated
    $messages = include $this->testDir . '/lang-zz/messages.php';
    expect($messages['hello'])->toBe('Hi');

    $validation = include $this->testDir . '/lang-zz/validation.php';
    expect($validation['required'])->toBe('Required');

    // Extra files NOT copied into lang-zz
    expect(file_exists($this->testDir . '/lang-zz/unregistered.php'))->toBeFalse();
    expect(file_exists($this->testDir . '/lang-zz/readme.txt'))->toBeFalse();
    expect(file_exists($this->testDir . '/lang-zz/subdir/other.php'))->toBeFalse();

    // Report
    $report = $lang->uploadReport;
    expect($report['summary']['total'])->toBe(2);
    expect($report['summary']['ok'])->toBe(2);
    expect($report['summary']['errors'])->toBe(0);
});

test('upload: PHP-corrupt file is reported as error and existing file kept untouched', function () {
    $zipPath = $this->testDir . '/zips/corrupt.zip';
    makeZip($zipPath, [
        // broken PHP — unterminated array, will throw ParseError on include
        'messages.php'   => "<?php return array('hello' =>",
        // valid
        'validation.php' => "<?php return array('required' => 'Needed') ?>",
    ]);

    $lang = makeTestLanguage($this->testDir);
    $validator = $lang->upload(makeUploadRequest($zipPath));

    expect($validator->fails())->toBeFalse();

    // messages.php — kept at original value because the upload was corrupt
    $messages = include $this->testDir . '/lang-zz/messages.php';
    expect($messages['hello'])->toBe('Hola');
    expect($messages['bye'])->toBe('Adios');

    // validation.php — successfully updated
    $validation = include $this->testDir . '/lang-zz/validation.php';
    expect($validation['required'])->toBe('Needed');

    // Report
    $report = $lang->uploadReport;
    expect($report['summary']['errors'])->toBe(1);
    expect($report['summary']['ok'])->toBe(1);

    $byId = collect($report['files'])->keyBy('id');
    expect($byId['messages']['status'])->toBe('error');
    expect($byId['messages']['details']['reason'])->toContain('syntax error');
    expect($byId['validation']['status'])->toBe('ok');
});

test('upload: extra keys dropped and missing keys restored from master', function () {
    $zipPath = $this->testDir . '/zips/locked.zip';
    makeZip($zipPath, [
        // 'bye' missing, 'extra' is not in master — should be dropped, 'bye' restored
        'messages.php'   => "<?php return array('hello' => 'Ciao', 'extra' => 'X') ?>",
        'validation.php' => "<?php return array('required' => 'Richiesto') ?>",
    ]);

    $lang = makeTestLanguage($this->testDir);
    $validator = $lang->upload(makeUploadRequest($zipPath));

    expect($validator->fails())->toBeFalse();

    $messages = include $this->testDir . '/lang-zz/messages.php';
    expect($messages)->toHaveKey('hello');
    expect($messages)->toHaveKey('bye');
    expect($messages)->not->toHaveKey('extra');
    expect($messages['hello'])->toBe('Ciao');         // user value kept
    expect($messages['bye'])->toBe('Goodbye');        // restored from master

    $report = $lang->uploadReport;
    $byId = collect($report['files'])->keyBy('id');

    expect($byId['messages']['status'])->toBe('locked');
    expect($byId['messages']['details']['extra_keys_dropped'])->toBe(1);
    expect($byId['messages']['details']['missing_keys_restored'])->toBe(1);

    expect($byId['validation']['status'])->toBe('ok');
    expect($report['summary']['locked'])->toBe(1);
});

test('upload: non-zip upload fails validation with a clear message', function () {
    $fakePath = $this->testDir . '/zips/not-a-zip.zip';
    File::put($fakePath, 'this is plain text, not a zip file');

    $lang = makeTestLanguage($this->testDir);
    $validator = $lang->upload(makeUploadRequest($fakePath));

    expect($validator->fails())->toBeTrue();
    $msg = $validator->errors()->first();
    expect($msg)->toBeString();
    expect(strtolower($msg))->toContain('zip');

    // Nothing touched
    $messages = include $this->testDir . '/lang-zz/messages.php';
    expect($messages['hello'])->toBe('Hola');
});