PHP Classes

File: tests/Feature/Commands/ExportCommandTest.php

Recommend this page to a friend!
  Packages of Alberto Arena   Laravel Truss   tests/Feature/Commands/ExportCommandTest.php   Download  
File: tests/Feature/Commands/ExportCommandTest.php
Role: Example script
Content type: text/plain
Description: Example script
Class: Laravel Truss
View a diagram of a Laravel application models
Author: By
Last change:
Date: 7 days ago
Size: 5,280 bytes
 

Contents

Class file image Download
<?php

declare(strict_types=1);

use
AlbertoArena\Truss\Export\SchemaExporter;
use
Illuminate\Support\Facades\Artisan;
use
Illuminate\Support\Facades\DB;
use
Illuminate\Support\Facades\Schema;

beforeEach(function () {
   
Schema::create('users', function ($table) {
       
$table->id();
       
$table->string('name');
    });
   
Schema::create('posts', function ($table) {
       
$table->id();
       
$table->foreignId('user_id')->constrained();
       
$table->string('title');
    });

   
// A unique path that does not yet exist; --output creates it.
   
$this->tmp = sys_get_temp_dir().'/truss-export-'.bin2hex(random_bytes(6)).'.out';
});

afterEach(function () {
    if (isset(
$this->tmp) && is_file($this->tmp)) {
       
unlink($this->tmp);
    }
});

it('defaults to DBML on stdout and exits 0', function () {
   
// Artisan::output() captures the raw multi-line body written to stdout, which
    // the line-oriented expectsOutput matcher does not handle for a bare write().
   
$code = Artisan::call('truss:export');
   
$out = Artisan::output();

   
expect($code)->toBe(0)
        ->
and($out)->toContain('Table posts {')
        ->
and($out)->toContain('Ref: posts.user_id > users.id');
});

it('exports JSON with --format=json', function () {
   
$this->artisan('truss:export', ['--format' => 'json', '--output' => $this->tmp])
        ->
assertExitCode(0);

   
$decoded = json_decode((string) file_get_contents($this->tmp), true);
   
expect(collect($decoded)->pluck('name')->all())->toBe(['posts', 'users']); // alphabetical
});

it('rejects an unknown format with exit code 2', function () {
   
$this->artisan('truss:export', ['--format' => 'yaml'])->assertExitCode(2);
});

it('keeps only the --tables allowlist', function () {
   
$this->artisan('truss:export', ['--tables' => 'users', '--output' => $this->tmp])
        ->
assertExitCode(0);

   
$out = (string) file_get_contents($this->tmp);
   
expect($out)->toContain('Table users {')->not->toContain('Table posts {');
});

it('drops the --exclude blocklist', function () {
   
$this->artisan('truss:export', ['--exclude' => 'posts', '--output' => $this->tmp])
        ->
assertExitCode(0);

   
$out = (string) file_get_contents($this->tmp);
   
expect($out)->toContain('Table users {')->not->toContain('Table posts {');
});

it('never exports a config-excluded table, even via --tables', function () {
   
config(['truss.excluded_tables' => ['posts']]);

   
$this->artisan('truss:export', ['--tables' => 'posts,users', '--output' => $this->tmp])
        ->
assertExitCode(0);

   
$out = (string) file_get_contents($this->tmp);
   
expect($out)->toContain('Table users {')->not->toContain('Table posts {');
});

it('exits 2 when nothing matches the filters', function () {
   
$this->artisan('truss:export', ['--tables' => 'does_not_exist'])->assertExitCode(2);
});

it('writes a file with --output and confirms', function () {
   
$this->artisan('truss:export', ['--output' => $this->tmp])
        ->
expectsOutputToContain("Wrote dbml export to {$this->tmp}.")
        ->
assertExitCode(0);

   
expect(file_get_contents($this->tmp))->toContain('Table posts {');
});

it('--check exits 0 when the file is up to date', function () {
   
$this->artisan('truss:export', ['--output' => $this->tmp])->assertExitCode(0);

   
$before = (string) file_get_contents($this->tmp);

   
$this->artisan('truss:export', ['--output' => $this->tmp, '--check' => true])
        ->
assertExitCode(0);

   
// --check writes nothing.
   
expect(file_get_contents($this->tmp))->toBe($before);
});

it('--check exits 1 on drift and writes nothing', function () {
   
file_put_contents($this->tmp, "stale contents\n");

   
$this->artisan('truss:export', ['--output' => $this->tmp, '--check' => true])
        ->
assertExitCode(1);

   
expect(file_get_contents($this->tmp))->toBe("stale contents\n");
});

it('--check exits 1 when the target file does not exist', function () {
   
// $this->tmp is a fresh, not-yet-created path.
   
$this->artisan('truss:export', ['--output' => $this->tmp, '--check' => true])
        ->
assertExitCode(1);

   
expect(is_file($this->tmp))->toBeFalse();
});

it('--check without --output is a usage error (exit 2)', function () {
   
$this->artisan('truss:export', ['--check' => true])->assertExitCode(2);
});

it('rejects an unmanaged connection with exit 2', function () {
   
$this->artisan('truss:export', ['--connection' => 'nope'])->assertExitCode(2);
});

it('exports a fresh snapshot with --fresh', function () {
   
$this->artisan('truss:export', ['--fresh' => true, '--output' => $this->tmp])
        ->
assertExitCode(0);

   
expect(file_get_contents($this->tmp))->toContain('Table posts {');
});

it('never leaks row data: the canary never appears in any format', function () {
   
Schema::create('secrets', function ($table) {
       
$table->id();
       
$table->string('token');
    });
   
DB::table('secrets')->insert(['token' => 'TRUSS_ROW_DATA_CANARY']);

    foreach (
SchemaExporter::formats() as $format) {
       
$out = $this->tmp.".{$format}";
       
$this->artisan('truss:export', ['--format' => $format, '--output' => $out])->assertExitCode(0);

       
expect(file_get_contents($out))->not->toContain('TRUSS_ROW_DATA_CANARY');
       
unlink($out);
    }
});