255 lines
9.5 KiB
Go
255 lines
9.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (roundTrip roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
return roundTrip(request)
|
|
}
|
|
|
|
func TestParseAdvertisement(t *testing.T) {
|
|
revision := "0123456789abcdef0123456789abcdef01234567"
|
|
tagObject := "89abcdef0123456789abcdef0123456789abcdef"
|
|
content := append(packet("# service=git-upload-pack\n"), []byte("0000")...)
|
|
content = append(content, packet(revision+" refs/heads/main\x00multi_ack\n")...)
|
|
content = append(content, packet(tagObject+" refs/tags/v1\n")...)
|
|
content = append(content, packet(revision+" refs/tags/v1^{}\n")...)
|
|
content = append(content, []byte("0000")...)
|
|
refs, err := parseAdvertisement(bytes.NewReader(content))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(refs) != 2 || refs["refs/heads/main"] != revision || refs["refs/tags/v1"] != revision {
|
|
t.Fatalf("refs = %#v", refs)
|
|
}
|
|
}
|
|
|
|
func TestParseAdvertisementKeepsLightweightTag(t *testing.T) {
|
|
revision := "0123456789abcdef0123456789abcdef01234567"
|
|
content := append(packet(revision+" refs/tags/v1\x00multi_ack\n"), []byte("0000")...)
|
|
refs, err := parseAdvertisement(bytes.NewReader(content))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(refs) != 1 || refs["refs/tags/v1"] != revision {
|
|
t.Fatalf("refs = %#v", refs)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryPath(t *testing.T) {
|
|
path, err := repositoryPath("https://git.quorate.dev/quorate/nuthatch.git")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if path != "quorate/nuthatch.git" {
|
|
t.Fatalf("path = %q", path)
|
|
}
|
|
}
|
|
|
|
func TestForgejoClientAuthenticatesRefAdvertisement(t *testing.T) {
|
|
revision := "0123456789abcdef0123456789abcdef01234567"
|
|
transport := roundTripFunc(func(request *http.Request) (*http.Response, error) {
|
|
if request.URL.RequestURI() != "/quorate/nuthatch.git/info/refs?service=git-upload-pack" {
|
|
t.Fatalf("request URI = %q", request.URL.RequestURI())
|
|
}
|
|
expected := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth2:test-token"))
|
|
if request.Header.Get("Authorization") != expected {
|
|
t.Fatal("missing source authorization")
|
|
}
|
|
content := append(packet(revision+" refs/heads/main\x00multi_ack\n"), []byte("0000")...)
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: http.Header{"Content-Type": {"application/x-git-upload-pack-advertisement"}},
|
|
Body: io.NopCloser(strings.NewReader(string(content))),
|
|
}, nil
|
|
})
|
|
|
|
client, err := newForgejoClient("https://forgejo.test", &http.Client{Transport: transport}, "test-token")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
refs, err := client.Refs(context.Background(), "https://git.quorate.dev/quorate/nuthatch")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if refs["refs/heads/main"] != revision {
|
|
t.Fatalf("refs = %#v", refs)
|
|
}
|
|
}
|
|
|
|
func TestForgejoClientIdentifiesSelectedPathTree(t *testing.T) {
|
|
first := "0123456789abcdef0123456789abcdef01234567"
|
|
second := "89abcdef0123456789abcdef0123456789abcdef"
|
|
packages := "1111111111111111111111111111111111111111"
|
|
runner := "2222222222222222222222222222222222222222"
|
|
transport := roundTripFunc(func(request *http.Request) (*http.Response, error) {
|
|
tree := request.URL.Path[strings.LastIndex(request.URL.Path, "/")+1:]
|
|
var body string
|
|
switch tree {
|
|
case first:
|
|
body = fmt.Sprintf(`{"tree":[{"path":"docs","mode":"040000","type":"tree","sha":"%s"},{"path":"packages","mode":"040000","type":"tree","sha":"%s"}],"page":1,"total_count":2}`, second, packages)
|
|
case second:
|
|
body = fmt.Sprintf(`{"tree":[{"path":"packages","mode":"040000","type":"tree","sha":"%s"}],"page":1,"total_count":1}`, packages)
|
|
case packages:
|
|
body = fmt.Sprintf(`{"tree":[{"path":"fsharp-runner","mode":"040000","type":"tree","sha":"%s"}],"page":1,"total_count":1}`, runner)
|
|
default:
|
|
t.Fatalf("unexpected tree %q", tree)
|
|
}
|
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body))}, nil
|
|
})
|
|
client, err := newForgejoClient("https://forgejo.test", &http.Client{Transport: transport}, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
firstIdentity, err := client.PathIdentity(context.Background(), "https://git.quorate.dev/ms2k/quorate", first, []string{"packages/fsharp-runner"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
secondIdentity, err := client.PathIdentity(context.Background(), "https://git.quorate.dev/ms2k/quorate", second, []string{"packages/fsharp-runner"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if firstIdentity != secondIdentity {
|
|
t.Fatalf("identities differ: %q != %q", firstIdentity, secondIdentity)
|
|
}
|
|
}
|
|
|
|
func TestForgejoClientReadsAllTreePages(t *testing.T) {
|
|
revision := "0123456789abcdef0123456789abcdef01234567"
|
|
target := "89abcdef0123456789abcdef0123456789abcdef"
|
|
requests := 0
|
|
transport := roundTripFunc(func(request *http.Request) (*http.Response, error) {
|
|
requests++
|
|
page, _ := strconv.Atoi(request.URL.Query().Get("page"))
|
|
entries := make([]string, 0, 100)
|
|
if page == 1 {
|
|
for index := range 100 {
|
|
entries = append(entries, fmt.Sprintf(`{"path":"entry-%03d","mode":"100644","type":"blob","sha":"%s"}`, index, target))
|
|
}
|
|
} else if page == 2 {
|
|
entries = append(entries, fmt.Sprintf(`{"path":"selected","mode":"100755","type":"blob","sha":"%s"}`, target))
|
|
}
|
|
body := fmt.Sprintf(`{"tree":[%s],"page":%d,"total_count":101}`, strings.Join(entries, ","), page)
|
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body))}, nil
|
|
})
|
|
client, err := newForgejoClient("https://forgejo.test", &http.Client{Transport: transport}, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.PathIdentity(context.Background(), "https://git.quorate.dev/ms2k/quorate", revision, []string{"selected"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if requests != 2 {
|
|
t.Fatalf("tree requests = %d", requests)
|
|
}
|
|
}
|
|
|
|
func TestForgejoClientIncludesMissingPathsInIdentity(t *testing.T) {
|
|
revision := "0123456789abcdef0123456789abcdef01234567"
|
|
transport := roundTripFunc(func(*http.Request) (*http.Response, error) {
|
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(`{"tree":[],"page":1,"total_count":0}`))}, nil
|
|
})
|
|
client, err := newForgejoClient("https://forgejo.test", &http.Client{Transport: transport}, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first, err := client.PathIdentity(context.Background(), "https://git.quorate.dev/ms2k/quorate", revision, []string{"missing-a"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := client.PathIdentity(context.Background(), "https://git.quorate.dev/ms2k/quorate", revision, []string{"missing-b"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first == second {
|
|
t.Fatal("different missing path scopes received the same identity")
|
|
}
|
|
}
|
|
|
|
func TestForgejoRouterKeepsCredentialsWithinAuthority(t *testing.T) {
|
|
revision := "0123456789abcdef0123456789abcdef01234567"
|
|
publicRequests := 0
|
|
privateRequests := 0
|
|
client := func(expectedToken string, requests *int) *http.Client {
|
|
return &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
|
|
(*requests)++
|
|
expected := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth2:"+expectedToken))
|
|
if request.Header.Get("Authorization") != expected {
|
|
t.Fatalf("authorization = %q", request.Header.Get("Authorization"))
|
|
}
|
|
content := append(packet(revision+" refs/heads/main\x00multi_ack\n"), []byte("0000")...)
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Body: io.NopCloser(bytes.NewReader(content)),
|
|
}, nil
|
|
})}
|
|
}
|
|
public, err := newForgejoClient("http://public.forgejo.test:3000", client("public-token", &publicRequests), "public-token")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
private, err := newForgejoClient("http://private.forgejo.test:3000", client("private-token", &privateRequests), "private-token")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
router, err := newForgejoRouter(
|
|
forgejoRoute{authority: "git.quorate.dev", source: public},
|
|
forgejoRoute{authority: "forge.quorate.internal", source: private},
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := router.Refs(context.Background(), "https://git.quorate.dev/ms2k/project"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if publicRequests != 1 || privateRequests != 0 {
|
|
t.Fatalf("requests public=%d private=%d", publicRequests, privateRequests)
|
|
}
|
|
if _, err := router.Refs(context.Background(), "https://forge.quorate.internal/quorate/cluster"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if publicRequests != 1 || privateRequests != 1 {
|
|
t.Fatalf("requests public=%d private=%d", publicRequests, privateRequests)
|
|
}
|
|
if _, err := router.Refs(context.Background(), "https://unknown.example/owner/repository"); err == nil {
|
|
t.Fatal("unknown authority received a Forgejo route")
|
|
}
|
|
if publicRequests != 1 || privateRequests != 1 {
|
|
t.Fatalf("unknown authority sent a request: public=%d private=%d", publicRequests, privateRequests)
|
|
}
|
|
}
|
|
|
|
func TestForgejoRouterRejectsDuplicateAuthority(t *testing.T) {
|
|
client, err := newForgejoClient("http://forgejo.test:3000", http.DefaultClient, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := newForgejoRouter(
|
|
forgejoRoute{authority: "git.quorate.dev", source: client},
|
|
forgejoRoute{authority: "GIT.QUORATE.DEV", source: client},
|
|
); err == nil {
|
|
t.Fatal("duplicate repository authority was accepted")
|
|
}
|
|
}
|
|
|
|
func TestRepositoryAuthorityRejectsCredentialBearingURL(t *testing.T) {
|
|
if _, err := repositoryAuthority("https://oauth2:[email protected]/quorate/nuthatch"); err == nil {
|
|
t.Fatal("credential-bearing repository URL was accepted")
|
|
}
|
|
}
|
|
|
|
func packet(value string) []byte {
|
|
return []byte(fmt.Sprintf("%04x%s", len(value)+4, value))
|
|
}
|