package server

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

func TestRepositoryIDAuthorizationTarget(t *testing.T) {
	t.Parallel()
	for _, test := range []struct {
		method     string
		path       string
		wantID     string
		wantAccess string
		want       bool
	}{
		{http.MethodGet, "/api/v3/repositories/42/environments", "42", "repo:read", true},
		{http.MethodPut, "/api/v3/repositories/42/environments/prod", "42", "repo:write", true},
		{http.MethodGet, "/api/v3/repositories/not-numeric/environments", "", "", false},
		{http.MethodGet, "/api/v3/repositories/42/actions", "", "", false},
	} {
		request := httptest.NewRequest(test.method, test.path, nil)
		id, access, ok := RepositoryIDAuthorizationTarget(request)
		if id != test.wantID || access != test.wantAccess || ok != test.want {
			t.Fatalf("RepositoryIDAuthorizationTarget(%s %s) = (%q, %q, %v), want (%q, %q, %v)", test.method, test.path, id, access, ok, test.wantID, test.wantAccess, test.want)
		}
	}
}

func TestRepositoryAuthorizationTargetKeepsSpecialReadsExact(t *testing.T) {
	t.Parallel()
	for _, test := range []struct {
		path string
		want bool
	}{
		{"/api/ext/v1/repos/owner/repo/summary", true},
		{"/api/ext/v1/repos/owner/repo/summary/extra", false},
		{"/api/v3/repos/owner/repo/assignees", true},
		{"/api/v3/repos/owner/repo/assignees/extra", false},
		{"/api/v3/repos/owner/repo/archive/refs/tags/v1.zip", true},
		{"/api/v3/repos/owner/repo/archive/refs/tags/v1.tar.gz", true},
		{"/api/v3/repos/owner/repo/archive/refs/tags/release%2Fcanary.tar.gz", true},
		{"/api/v3/repos/owner/repo/archive/refs/tags/.zip", false},
		{"/api/v3/repos/owner/repo/archive/refs/tags/.tar.gz", false},
		{"/api/v3/repos/owner/repo/archive/refs/tags/v1.zip/extra", false},
		{"/api/v3/repos/owner/repo/archive/refs/tags/v1.rar", false},
		{"/api/v3/repos/owner%2Fevil/repo", false},
		{"/api/v3/repos/owner/repo%2Fevil", false},
	} {
		request := httptest.NewRequest(http.MethodGet, test.path, nil)
		repository, capability, ok := RepositoryAuthorizationTarget(request)
		if ok != test.want {
			t.Fatalf("RepositoryAuthorizationTarget(GET %s) ok=%v, want %v", test.path, ok, test.want)
		}
		if ok && (repository != "owner/repo" || capability != "repo:read") {
			t.Fatalf("RepositoryAuthorizationTarget(GET %s)=(%q,%q), want owner/repo,repo:read", test.path, repository, capability)
		}
	}
}

func TestRepositoryAuthorizationTargetRejectsInvalidArchiveRawPath(t *testing.T) {
	t.Parallel()
	for _, requestURL := range []*url.URL{
		{Path: "/api/v3/repos/owner/repo/archive/refs/tags/v1.zip", RawPath: "/api/v3/repos/owner/repo/archive/refs/tags/%zz.zip"},
		{Path: "/api/v3/repos/owner/repo/archive/refs/tags/v1.zip", RawPath: "/api/v3/repos/owner/repo/archive/refs/tags/v2.zip"},
	} {
		request := &http.Request{Method: http.MethodGet, URL: requestURL}
		_, _, ok := RepositoryAuthorizationTarget(request)
		if ok {
			t.Fatalf("RepositoryAuthorizationTarget accepted invalid RawPath %q for Path %q", requestURL.RawPath, requestURL.Path)
		}
	}

	request := httptest.NewRequest(http.MethodGet, "/api/v3/repos/owner/repo/archive/refs/tags/release%252Fcanary.tar.gz", nil)
	_, _, ok := RepositoryAuthorizationTarget(request)
	if !ok {
		t.Fatal("RepositoryAuthorizationTarget rejected a tag decoded exactly once")
	}
}

func TestGitSmartHTTPAuthorizationTargetKeepsRegisteredOperationsExact(t *testing.T) {
	t.Parallel()
	for _, test := range []struct {
		method, path string
		want         bool
		capability   string
	}{
		{http.MethodGet, "/owner/repo.git/info/refs?service=git-upload-pack", true, "repo:clone"},
		{http.MethodGet, "/owner/repo.git/info/refs?service=git-receive-pack", true, "repo:write"},
		{http.MethodGet, "/owner/repo.git/info/refs", true, "repo:clone"},
		{http.MethodPost, "/owner/repo.git/git-upload-pack", true, "repo:clone"},
		{http.MethodPost, "/owner/repo.git/git-receive-pack", true, "repo:write"},
		{http.MethodPost, "/owner/repo.git/info/refs?service=git-upload-pack", false, ""},
		{http.MethodGet, "/owner/repo.git/git-upload-pack", false, ""},
		{http.MethodGet, "/owner/repo.git/info/refs?service=unknown", false, ""},
		{http.MethodGet, "/owner/repo.git/unknown", false, ""},
		{http.MethodGet, "/owner/repo.git/info/refs/extra", false, ""},
		{http.MethodGet, "/owner/.git/info/refs", false, ""},
	} {
		request := httptest.NewRequest(test.method, test.path, nil)
		repository, capability, ok := GitSmartHTTPAuthorizationTarget(request)
		if ok != test.want || capability != test.capability {
			t.Fatalf("GitSmartHTTPAuthorizationTarget(%s %s)=(%q,%q,%v), want owner/repo,%q,%v", test.method, test.path, repository, capability, ok, test.capability, test.want)
		}
		if ok && repository != "owner/repo" {
			t.Fatalf("GitSmartHTTPAuthorizationTarget(%s %s) repository=%q, want owner/repo", test.method, test.path, repository)
		}
	}
}
