ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
255
services/project-delivery/controller/forgejo_test.go
Normal file
255
services/project-delivery/controller/forgejo_test.go
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
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))
|
||||
}
|
||||
Loading…
Reference in a new issue