// Pass 4 — security-sensitive manifest validation tests.
//
// Locks in the regex / path-traversal / NUL-byte rules added in Pass 2 so
// regressions in Manifest.Validate are caught at CI time instead of via
// manual review.
package plugin

import (
	"strings"
	"testing"
)

func TestPluginNameRegexp(t *testing.T) {
	cases := []struct {
		name string
		ok   bool
	}{
		{"hello", true},
		{"game-detection", true},
		{"a", true},
		{"a1_b-c", true},
		{"abcdefghijklmnopqrstuvwxyz0123456789_-abcdefghijklmnopqrstuvwxyz", true},   // 64 chars
		{"abcdefghijklmnopqrstuvwxyz0123456789_-abcdefghijklmnopqrstuvwxyz0", false}, // 65 chars
		{"", false},
		{"Hello", false},
		{"_leading", false},
		{"-leading", false},
		{"..", false},
		{"a/b", false},
		{"a.b", false},
		{"a b", false},
		{"a\x00b", false},
		{"hello!", false},
	}
	for _, c := range cases {
		got := pluginNameRegexp.MatchString(c.name)
		if got != c.ok {
			t.Errorf("pluginNameRegexp.MatchString(%q) = %v, want %v", c.name, got, c.ok)
		}
	}
}

func TestValidateRelativePath(t *testing.T) {
	cases := []struct {
		path string
		ok   bool
	}{
		{"a.wasm", true},
		{"assets/index.html", true},
		{"dir/sub/file.js", true},
		{"hello.wasm", true},
		// failures
		{"", false},
		{"/abs/path", false},
		{"../escape", false},
		{"./not-clean", false},
		{"dir//double", false},
		{"dir/", false},
		{"dir\\win", false},
		{"file\x00name", false},
		{"..", false},
		{".", false},
		{"a/../b", false},
		{"a/./b", false},
	}
	for _, c := range cases {
		err := validateRelativePath(c.path)
		got := err == nil
		if got != c.ok {
			t.Errorf("validateRelativePath(%q) ok=%v err=%v, want ok=%v", c.path, got, err, c.ok)
		}
	}
}

func TestManifestValidateRejectsBadName(t *testing.T) {
	m := &Manifest{
		Name:       "Bad Name",
		Version:    "0.1.0",
		Entrypoint: "hello.wasm",
	}
	if err := m.Validate(); err == nil {
		t.Fatal("expected validation failure for invalid name")
	}
}

func TestManifestValidateRejectsBadAsset(t *testing.T) {
	m := &Manifest{
		Name:       "hello",
		Version:    "0.1.0",
		Entrypoint: "hello.wasm",
		UI: UISpec{
			Tabs: []UITab{
				{ID: "main", Asset: "../../../etc/passwd"},
			},
		},
	}
	if err := m.Validate(); err == nil {
		t.Fatal("expected validation failure for traversal asset")
	}
}

func TestManifestValidateRejectsAbsoluteEntrypoint(t *testing.T) {
	m := &Manifest{
		Name:       "hello",
		Version:    "0.1.0",
		Entrypoint: "/etc/passwd.wasm",
	}
	if err := m.Validate(); err == nil {
		t.Fatal("expected validation failure for absolute entrypoint")
	}
}

func TestManifestValidateAcceptsMinimal(t *testing.T) {
	m := &Manifest{
		Name:       "hello",
		Version:    "0.1.0",
		Entrypoint: "hello.wasm",
	}
	if err := m.Validate(); err != nil {
		t.Fatalf("expected minimal manifest to validate, got: %v", err)
	}
}

func TestManifestValidateRejectsOversizedVersion(t *testing.T) {
	m := &Manifest{
		Name:       "hello",
		Version:    strings.Repeat("v", 65),
		Entrypoint: "hello.wasm",
	}
	if err := m.Validate(); err == nil {
		t.Fatal("expected validation failure for oversized version")
	}
}

func TestManifestValidateRejectsUnknownPermission(t *testing.T) {
	m := &Manifest{
		Name:        "hello",
		Version:     "0.1.0",
		Entrypoint:  "hello.wasm",
		Permissions: []string{"commands", "filesystem"},
	}
	if err := m.Validate(); err == nil {
		t.Fatal("expected validation failure for unknown permission")
	}
}
