Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 40 additions & 34 deletions tests/Feature/Commands/InitCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
use MathiasGrimm\GlimpseCli\Support\IgnoreFile;
use Symfony\Component\Process\Process;

// Raw Artisan::call('init') runs must pass --no-interaction. Interactivity
// follows the suite's stdin, so on a TTY the seed and workflow confirms
// would block waiting for an answer, with the prompt invisible inside the
// captured output. Tests that answer a prompt use $this->artisan() with
// expectsConfirmation() instead.

const INIT_SEED_QUESTION = 'Scan the current directory and record every image into the baseline now (runs analyze . --update-baseline)?';

const INIT_WORKFLOW_QUESTION = 'Add a GitHub Actions workflow that runs glimpse check on pull requests and pushes to main (.github/workflows/glimpse.yml)?';
Expand Down Expand Up @@ -44,7 +50,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
chdirWorkspace();
Http::fake();

expect(Artisan::call('init'))->toBe(0);
expect(Artisan::call('init', ['--no-interaction' => true]))->toBe(0);

$output = Artisan::output();

Expand Down Expand Up @@ -101,7 +107,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(fakeAnalyzeResponse())]);

expect(Artisan::call('init', ['--update-baseline' => true]))->toBe(0)
expect(Artisan::call('init', ['--update-baseline' => true, '--no-interaction' => true]))->toBe(0)
->and(baselineFiles())->toBe(['photo.png' => baselineEntry(workspace().'/photo.png')]);
});

Expand All @@ -112,7 +118,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(fakeAnalyzeResponse())]);

expect(Artisan::call('init', ['--update-baseline' => true]))->toBe(0)
expect(Artisan::call('init', ['--update-baseline' => true, '--no-interaction' => true]))->toBe(0)
->and(baselineFiles())->toBe(['photo.png' => baselineEntry(workspace().'/photo.png')]);

Http::assertSentCount(1);
Expand All @@ -121,7 +127,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
test('re-running on a configured project keeps both files and exits 0', function () {
chdirWorkspace();
Http::fake();
Artisan::call('init');
Artisan::call('init', ['--no-interaction' => true]);

$ignoreBefore = (string) file_get_contents(ignorePath());
$baselineBefore = (string) file_get_contents(baselinePath());
Expand All @@ -142,7 +148,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
Http::fake();
file_put_contents(ignorePath(), "custom-pattern/\n");

expect(Artisan::call('init'))->toBe(0)
expect(Artisan::call('init', ['--no-interaction' => true]))->toBe(0)
->and((string) file_get_contents(ignorePath()))->toBe("custom-pattern/\n")
->and(baselineFiles())->toBe([]);

Expand All @@ -156,7 +162,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
$entry = baselineEntry(createImage('photo.png'));
writeBaseline(['photo.png' => $entry]);

expect(Artisan::call('init', ['--force' => true]))->toBe(0)
expect(Artisan::call('init', ['--force' => true, '--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->toContain('Recreated '.IgnoreFile::FILENAME.' from the starter template.');

$ignore = (string) file_get_contents(ignorePath());
Expand Down Expand Up @@ -189,7 +195,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(['message' => 'Unauthenticated.'], 401)]);

expect(Artisan::call('init', ['--update-baseline' => true]))->toBe(1);
expect(Artisan::call('init', ['--update-baseline' => true, '--no-interaction' => true]))->toBe(1);

$output = Artisan::output();

Expand All @@ -205,7 +211,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(fakeAnalyzeResponse())]);

expect(Artisan::call('init', ['--update-baseline' => true]))->toBe(0);
expect(Artisan::call('init', ['--update-baseline' => true, '--no-interaction' => true]))->toBe(0);

$output = Artisan::output();

Expand All @@ -220,20 +226,20 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(fakeAnalyzeResponse())]);

Artisan::call('init', ['--update-baseline' => true]);
Artisan::call('init', ['--update-baseline' => true, '--no-interaction' => true]);
Artisan::output();

chdirWorkspace(workspace().'/fresh');

expect(Artisan::call('init'))->toBe(0)
expect(Artisan::call('init', ['--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->toContain('Accept the current images as already handled');
});

test('next steps include the seed hint on a scaffold-only run', function () {
chdirWorkspace();
Http::fake();

Artisan::call('init');
Artisan::call('init', ['--no-interaction' => true]);
$output = Artisan::output();

expect($output)->toContain('Next steps:')
Expand All @@ -248,7 +254,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(fakeAnalyzeResponse())]);

Artisan::call('init', ['--update-baseline' => true]);
Artisan::call('init', ['--update-baseline' => true, '--no-interaction' => true]);
$output = Artisan::output();

expect($output)->toContain('Next steps:')
Expand Down Expand Up @@ -317,7 +323,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
mkdir(workspace().'/.git');
Http::fake();

expect(Artisan::call('init'))->toBe(0)
expect(Artisan::call('init', ['--no-interaction' => true]))->toBe(0)
->and(is_file(workflowPath()))->toBeFalse()
->and(Artisan::output())->toContain('Gate new images in CI: glimpse check .');
});
Expand All @@ -326,7 +332,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
chdirWorkspace();
Http::fake();

expect(Artisan::call('init', ['--workflow' => true]))->toBe(0);
expect(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(0);

$output = Artisan::output();

Expand Down Expand Up @@ -359,7 +365,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
$content = writeWorkflow();
Http::fake();

expect(Artisan::call('init', ['--workflow' => true]))->toBe(0)
expect(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->toContain('already exists, kept (use --workflow --force to recreate it).')
->and((string) file_get_contents(workflowPath()))->toBe($content);
});
Expand All @@ -369,7 +375,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
writeWorkflow();
Http::fake();

expect(Artisan::call('init', ['--workflow' => true, '--force' => true]))->toBe(0)
expect(Artisan::call('init', ['--workflow' => true, '--force' => true, '--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->toContain('Recreated '.InitCommand::WORKFLOW_PATH.' from the starter template.')
->and((string) file_get_contents(workflowPath()))->toBe(InitCommand::WORKFLOW_TEMPLATE);
});
Expand All @@ -379,7 +385,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
$content = writeWorkflow();
Http::fake();

expect(Artisan::call('init', ['--force' => true]))->toBe(0)
expect(Artisan::call('init', ['--force' => true, '--no-interaction' => true]))->toBe(0)
->and((string) file_get_contents(workflowPath()))->toBe($content);
});

Expand All @@ -389,7 +395,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(['message' => 'Unauthenticated.'], 401)]);

expect(Artisan::call('init', ['--update-baseline' => true, '--workflow' => true]))->toBe(1)
expect(Artisan::call('init', ['--update-baseline' => true, '--workflow' => true, '--no-interaction' => true]))->toBe(1)
->and((string) file_get_contents(workflowPath()))->toBe(InitCommand::WORKFLOW_TEMPLATE);
});

Expand All @@ -398,7 +404,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
file_put_contents(workspace().'/.github', 'not a directory');
Http::fake();

expect(Artisan::call('init', ['--workflow' => true]))->toBe(1)
expect(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(1)
->and(Artisan::output())->toContain('Could not create the directory')
->and(is_file(workflowPath()))->toBeFalse();
});
Expand All @@ -410,7 +416,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
symlink(workspace().'/target.yml', workflowPath());
Http::fake();

expect(Artisan::call('init', ['--workflow' => true, '--force' => true]))->toBe(1)
expect(Artisan::call('init', ['--workflow' => true, '--force' => true, '--no-interaction' => true]))->toBe(1)
->and(Artisan::output())->toContain('is a symbolic link')
->and((string) file_get_contents(workspace().'/target.yml'))->toBe("original\n");
});
Expand All @@ -419,7 +425,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
chdirWorkspace();
Http::fake();

Artisan::call('init', ['--workflow' => true]);
Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]);
Artisan::output();

// The written state must not leak into a project whose workflow
Expand All @@ -428,15 +434,15 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
writeWorkflow(directory: $kept);
chdirWorkspace($kept);

Artisan::call('init');
Artisan::call('init', ['--no-interaction' => true]);
$output = Artisan::output();

expect($output)->toContain('Review '.InitCommand::WORKFLOW_PATH)
->and($output)->not->toContain('set the GLIMPSE_TOKEN secret for higher rate limits');

chdirWorkspace(workspace().'/fresh');

Artisan::call('init');
Artisan::call('init', ['--no-interaction' => true]);
$output = Artisan::output();

expect($output)->toContain('Gate new images in CI')
Expand Down Expand Up @@ -476,7 +482,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
file_put_contents(workspace().'/.github/workflows', 'not a directory');
Http::fake();

expect(Artisan::call('init', ['--workflow' => true]))->toBe(1)
expect(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(1)
->and(Artisan::output())->toContain('Could not create the directory')
->and((string) file_get_contents(workspace().'/.github/workflows'))->toBe('not a directory');
});
Expand All @@ -488,7 +494,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
symlink(workspace().'/elsewhere', workspace().'/.github/workflows');
Http::fake();

expect(Artisan::call('init', ['--workflow' => true]))->toBe(1)
expect(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(1)
->and(Artisan::output())->toContain('is a symbolic link')
->and(glob(workspace().'/elsewhere/*'))->toBe([]);
});
Expand Down Expand Up @@ -518,7 +524,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director

// Artisan::call cannot answer the seed confirm, so it falls through
// to No: the exact shape of every scripted `init --workflow` run.
expect(Artisan::call('init', ['--workflow' => true]))->toBe(0)
expect(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->toContain(INIT_EMPTY_BASELINE_WARNING);

Http::assertNothingSent();
Expand All @@ -529,7 +535,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
writeWorkflow();
Http::fake();

expect(Artisan::call('init'))->toBe(0)
expect(Artisan::call('init', ['--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->toContain(INIT_EMPTY_BASELINE_WARNING);
});

Expand All @@ -539,7 +545,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(fakeAnalyzeResponse())]);

expect(Artisan::call('init', ['--workflow' => true, '--update-baseline' => true]))->toBe(0)
expect(Artisan::call('init', ['--workflow' => true, '--update-baseline' => true, '--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->not->toContain('first CI run will re-check');
});

Expand All @@ -548,7 +554,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
createImage('photo.png');
Http::fake();

expect(Artisan::call('init'))->toBe(0)
expect(Artisan::call('init', ['--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->not->toContain('first CI run will re-check');
});

Expand All @@ -560,8 +566,8 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
// First scripted run scaffolds the empty baseline, the second adds
// the workflow: the trap state is reached across two runs, so the
// warning must key on the file content, not on what this run did.
expect(Artisan::call('init'))->toBe(0)
->and(Artisan::call('init', ['--workflow' => true]))->toBe(0)
expect(Artisan::call('init', ['--no-interaction' => true]))->toBe(0)
->and(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->toContain(INIT_EMPTY_BASELINE_WARNING);
});

Expand All @@ -571,7 +577,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
writeBaseline(['photo.png' => $entry]);
Http::fake();

expect(Artisan::call('init', ['--workflow' => true]))->toBe(0)
expect(Artisan::call('init', ['--workflow' => true, '--no-interaction' => true]))->toBe(0)
->and(Artisan::output())->not->toContain('first CI run will re-check');
});

Expand All @@ -581,7 +587,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director
putenv('GLIMPSE_TOKEN=test-token');
Http::fake(['*/v1/analyze' => Http::response(['message' => 'Unauthenticated.'], 401)]);

expect(Artisan::call('init', ['--update-baseline' => true, '--workflow' => true]))->toBe(1)
expect(Artisan::call('init', ['--update-baseline' => true, '--workflow' => true, '--no-interaction' => true]))->toBe(1)
->and(Artisan::output())->toContain(INIT_EMPTY_BASELINE_WARNING);
});

Expand All @@ -598,7 +604,7 @@ function writeWorkflow(string $content = "custom: workflow\n", ?string $director

// The refresh fails but the populated baseline on disk is intact,
// so CI behaves exactly as before the run: no warning.
expect(Artisan::call('init', ['--update-baseline' => true, '--workflow' => true]))->toBe(1)
expect(Artisan::call('init', ['--update-baseline' => true, '--workflow' => true, '--no-interaction' => true]))->toBe(1)
->and(Artisan::output())->not->toContain('The baseline is empty');
});
});
Loading