seed(\Database\Seeders\PermissionSeeder::class);
$this->seed(\Database\Seeders\RoleSeeder::class);
$this->admin = User::factory()->create();
$adminRole = Role::where('slug', 'admin')->first();
// Ensure the admin role has the new permission
$editThemesPermission = \App\Models\Permission::where('slug', 'edit-themes')->first();
$adminRole->permissions()->syncWithoutDetaching([$editThemesPermission->id]);
$this->admin->roles()->attach($adminRole);
$this->themePath = base_path('themes/' . $this->themeName);
if (!File::exists($this->themePath)) {
File::makeDirectory($this->themePath, 0755, true);
File::put($this->themePath . '/theme.md', "Title: Test Editor Theme\nAuthor: Junie");
File::put($this->themePath . '/index.blade.php', "
Hello
");
File::makeDirectory($this->themePath . '/assets/css', 0755, true);
File::put($this->themePath . '/assets/css/style.css', "body { color: red; }");
}
}
protected function tearDown(): void
{
if (File::exists($this->themePath)) {
File::deleteDirectory($this->themePath);
}
parent::tearDown();
}
public function test_admin_can_view_theme_editor_index()
{
$response = $this->actingAs($this->admin)->get('/loom/themes/editor');
$response->assertStatus(200);
$response->assertViewIs('admin.themes.editor');
$response->assertSee('test-editor-theme');
}
public function test_admin_can_get_file_tree()
{
$response = $this->actingAs($this->admin)->getJson('/loom/themes/editor/tree?theme=' . $this->themeName);
$response->assertStatus(200);
$data = $response->json();
$this->assertIsArray($data);
// Find index.blade.php
$indexFile = collect($data)->firstWhere('name', 'index.blade.php');
$this->assertNotNull($indexFile);
$this->assertEquals('file', $indexFile['type']);
// Find assets directory
$assetsDir = collect($data)->firstWhere('name', 'assets');
$this->assertNotNull($assetsDir);
$this->assertEquals('directory', $assetsDir['type']);
$this->assertNotEmpty($assetsDir['children']);
}
public function test_admin_can_read_file_content()
{
$response = $this->actingAs($this->admin)->getJson('/loom/themes/editor/read?theme=' . $this->themeName . '&path=index.blade.php');
$response->assertStatus(200);
$response->assertJson([
'content' => "Hello
",
'extension' => 'php'
]);
}
public function test_admin_can_save_file_content_and_creates_bak()
{
$newContent = "Updated
";
$response = $this->actingAs($this->admin)->postJson('/loom/themes/editor/save', [
'theme' => $this->themeName,
'path' => 'index.blade.php',
'content' => $newContent
]);
$response->assertStatus(200);
$response->assertJson(['success' => true]);
$this->assertEquals($newContent, File::get($this->themePath . '/index.blade.php'));
$this->assertTrue(File::exists($this->themePath . '/index.blade.php.bak'));
$this->assertEquals("Hello
", File::get($this->themePath . '/index.blade.php.bak'));
}
public function test_admin_can_create_new_file()
{
$response = $this->actingAs($this->admin)->postJson('/loom/themes/editor/create', [
'theme' => $this->themeName,
'path' => 'assets/css',
'filename' => 'new.css'
]);
$response->assertStatus(200);
$this->assertTrue(File::exists($this->themePath . '/assets/css/new.css'));
}
public function test_directory_traversal_protection()
{
// Try to read a file outside themes
$response = $this->actingAs($this->admin)->getJson('/loom/themes/editor/read?theme=' . $this->themeName . '&path=../../.env');
$response->assertStatus(403);
}
public function test_invalid_extension_protection()
{
$response = $this->actingAs($this->admin)->postJson('/loom/themes/editor/create', [
'theme' => $this->themeName,
'path' => '',
'filename' => 'evil.exe'
]);
$response->assertStatus(422);
}
}