package knotfeed
import (
"fmt"
"reflect"
"strings"
"testing"
"tangled.org/core/tapc"
)
const (
legacyNewSha = "bcfa6359b037bc411e9626260cc3dd9a28408d2c"
legacyOldSha = "9eab6c4f29bc9e609ec52888cd6a4ef716b567c9"
zeroSha1 = "0000000000000000000000000000000000000000"
)
const legacyRefFrame = `{
"rkey": "3mug",
"nsid": "sh.tangled.git.refUpdate",
"created": 1788245839553422000,
"event": {
"$type": "sh.tangled.git.refUpdate",
"ref": "refs/heads/iso16283-low-frequency-corner",
"oldSha": "9eab6c4f29bc9e609ec52888cd6a4ef716b567c9",
"newSha": "bcfa6359b037bc411e9626260cc3dd9a28408d2c",
"committerDid": "did:plc:jder4wkjnwh6mzhrx4n5ebti",
"ownerDid": "did:plc:jder4wkjnwh6mzhrx4n5ebti",
"repo": "did:plc:5i6o2hrw2viwpxgicwad7f2h",
"pushOptions": ["skip-ci"],
"changedFiles": ["tests/building/measurement/test_low_frequency.py"],
"meta": {"isDefaultRef": false, "commitCount": {"byEmail": [{"email": "mail@jmrp.io", "count": 1}]}}
}
}`
func refFrame(ref, repo, oldSha, newSha string) string {
return fmt.Sprintf(
`{"nsid":"sh.tangled.git.refUpdate","created":11,`+
`"event":{"ref":%q,"repo":%q,"oldSha":%q,"newSha":%q}}`,
ref, repo, oldSha, newSha)
}
func sha(t *testing.T, hex string) ObjectID {
t.Helper()
id, ok := ParseObjectID(hex)
if !ok {
t.Fatalf("ParseObjectID(%q)", hex)
}
return id
}
func refOp(t *testing.T, refname, oldSha string, rec RefRecord) RecordOp {
t.Helper()
rkey, ok := EscapeRefname(refname)
if !ok {
t.Fatalf("EscapeRefname(%q)", refname)
}
op := RecordOp{
Action: tapc.RecordDeleteAction,
Collection: GitRefCollection,
Rkey: rkey,
Prior: ParsePriorSha(oldSha),
}
if rec.Sha.IsZero() {
return op
}
record, err := encodeRefRecord(rec)
if err != nil {
t.Fatalf("encodeRefRecord: %v", err)
}
op.Action, op.Bytes = tapc.RecordCreateAction, record
return op
}
func TestLegacyFrameReadsAsCommit(t *testing.T) {
for _, tt := range []struct {
name string
frame string
want Commit
}{
{
name: "push",
frame: legacyRefFrame,
want: Commit{
Repo: "did:plc:5i6o2hrw2viwpxgicwad7f2h",
Seq: 1788245839553422000,
Records: []RecordOp{refOp(t, "refs/heads/iso16283-low-frequency-corner", legacyOldSha,
RefRecord{
Sha: sha(t, legacyNewSha),
Editor: "did:plc:jder4wkjnwh6mzhrx4n5ebti",
PushOptions: []string{"skip-ci"},
})},
},
},
{
name: "deleted branch",
frame: refFrame("refs/heads/gone", "did:plc:scallop", legacyOldSha, zeroSha1),
want: Commit{
Repo: "did:plc:scallop",
Seq: 11,
Records: []RecordOp{refOp(t, "refs/heads/gone", legacyOldSha, RefRecord{})},
},
},
{
name: "deleted branch in a sha256 repo",
frame: refFrame("refs/heads/gone", "did:plc:scallop",
strings.Repeat("ab", 32), strings.Repeat("0", 64)),
want: Commit{
Repo: "did:plc:scallop",
Seq: 11,
Records: []RecordOp{refOp(t, "refs/heads/gone", strings.Repeat("ab", 32), RefRecord{})},
},
},
{
name: "brand new branch",
frame: refFrame("refs/heads/new", "did:plc:scallop", zeroSha1, legacyNewSha),
want: Commit{
Repo: "did:plc:scallop",
Seq: 11,
Records: []RecordOp{refOp(t, "refs/heads/new", zeroSha1,
RefRecord{Sha: sha(t, legacyNewSha), Editor: "did:plc:scallop"})},
},
},
{
name: "push that left oldSha empty",
frame: refFrame("refs/heads/new", "did:plc:scallop", "", legacyNewSha),
want: Commit{
Repo: "did:plc:scallop",
Seq: 11,
Records: []RecordOp{refOp(t, "refs/heads/new", "",
RefRecord{Sha: sha(t, legacyNewSha), Editor: "did:plc:scallop"})},
},
},
{
name: "push from a pre-1.14 knot, which names the repo repoDid",
frame: `{"nsid":"sh.tangled.git.refUpdate","created":11,"event":{` +
`"ref":"refs/heads/main","ownerDid":"did:plc:clam","repoDid":"did:plc:scallop",` +
`"repoName":"shell","committerDid":"did:plc:clam",` +
`"oldSha":"` + legacyOldSha + `","newSha":"` + legacyNewSha + `"}}`,
want: Commit{
Repo: "did:plc:scallop",
Seq: 11,
Records: []RecordOp{refOp(t, "refs/heads/main", legacyOldSha,
RefRecord{Sha: sha(t, legacyNewSha), Editor: "did:plc:clam"})},
},
},
{
name: "push with an empty committerDid",
frame: `{"nsid":"sh.tangled.git.refUpdate","created":11,` +
`"event":{"ref":"refs/heads/main","repo":"did:plc:scallop","committerDid":"",` +
`"oldSha":"` + legacyOldSha + `","newSha":"` + legacyNewSha + `"}}`,
want: Commit{
Repo: "did:plc:scallop",
Seq: 11,
Records: []RecordOp{refOp(t, "refs/heads/main", legacyOldSha,
RefRecord{Sha: sha(t, legacyNewSha), Editor: "did:plc:scallop"})},
},
},
{
name: "collaborator removal",
frame: `{"nsid":"sh.tangled.repo.collaboratorUpdate","created":9,
"event":{"op":"remove","subject":"did:plc:olaren","repo":"did:plc:scallop"}}`,
want: Commit{
Repo: "did:plc:scallop",
Seq: 9,
Records: []RecordOp{{
Action: tapc.RecordDeleteAction,
Collection: collaboratorInviteCollection,
Rkey: "did:plc:olaren",
}},
},
},
{
name: "knot member update",
frame: `{"nsid":"sh.tangled.knot.memberUpdate","created":11,
"event":{"op":"add","subject":"did:plc:limpet"}}`,
want: Commit{Seq: 11},
},
{
name: "ref update with an empty sha",
frame: refFrame("refs/heads/main", "did:plc:scallop", legacyOldSha, ""),
want: Commit{Seq: 11},
},
{
name: "ref update with an empty refname",
frame: refFrame("", "did:plc:scallop", legacyOldSha, legacyNewSha),
want: Commit{Seq: 11},
},
{
name: "new sha with shell junk in it",
frame: refFrame("refs/heads/main", "did:plc:scallop", legacyOldSha, "HEAD~1; rm -rf /"),
want: Commit{Seq: 11},
},
} {
t.Run(tt.name, func(t *testing.T) {
msg, err := decodeLegacy([]byte(tt.frame), nil)
if err != nil {
t.Fatalf("decodeLegacy: %v", err)
}
if msg.Type != TypeCommit || msg.Commit == nil {
t.Fatalf("message type = %q, want a commit", msg.Type)
}
if !reflect.DeepEqual(*msg.Commit, tt.want) {
t.Errorf("commit = %+v, want %+v", *msg.Commit, tt.want)
}
})
}
}
func TestLegacyFrameWithoutCursorErrorsOut(t *testing.T) {
if _, err := decodeLegacy([]byte(`{"nsid":"sh.tangled.git.refUpdate"}`), nil); err == nil {
t.Error("frame without a created stamp decoded anyway")
}
}
func TestOnlyObjectIdReadsAsPriorSha(t *testing.T) {
for _, reported := range []string{"", "def456", "HEAD", strings.Repeat("zz", 20), zeroSha1 + "0"} {
if id, known := ParsePriorSha(reported).Sha(); known {
t.Errorf("ParsePriorSha(%q) = %q, want an unknown prior", reported, id)
}
}
if _, known := (RecordOp{}).Prior.Sha(); known {
t.Error("zero-value RecordOp came back with a known prior sha")
}
hex := strings.Repeat("ab", 32)
if got, known := ParsePriorSha(hex).Sha(); !known || got.String() != hex {
t.Errorf("ParsePriorSha(%q) = (%q, %v), want the sha back", hex, got, known)
}
if got, known := ParsePriorSha(zeroSha1).Sha(); !known || !got.IsZero() {
t.Errorf("ParsePriorSha(%q) = (%q, %v), want a known absence", zeroSha1, got, known)
}
}
func TestParseObjectIDTakesOnlyRealObjectIds(t *testing.T) {
for _, hex := range []string{
"", "def456", strings.Repeat("ab", 21), strings.Repeat("zz", 20), "HEAD~1; rm -rf /",
} {
if id, ok := ParseObjectID(hex); ok {
t.Errorf("ParseObjectID(%q) = %q, want a refusal", hex, id)
}
}
for _, hex := range []string{zeroSha1, strings.Repeat("0", 64)} {
id, ok := ParseObjectID(hex)
if !ok || !id.IsZero() {
t.Errorf("ParseObjectID(%q) = (%q, %v), want the null object id", hex, id, ok)
}
}
for _, hex := range []string{legacyNewSha, strings.Repeat("AB", 20), strings.Repeat("cD", 32)} {
id, ok := ParseObjectID(hex)
if !ok || id.String() != hex {
t.Errorf("ParseObjectID(%q) = (%q, %v), want the sha back", hex, id, ok)
}
}
}
func TestLegacyFrameWithUnusableFieldsIsRefused(t *testing.T) {
for _, tt := range []struct {
name string
frame string
}{
{"nsid that isn't an nsid", `{"nsid":"not an nsid","created":11}`},
{"repo that isn't a did", refFrame("refs/heads/main", "scallop", legacyOldSha, legacyNewSha)},
{"collaborator subject that isn't a did", `{"nsid":"sh.tangled.repo.collaboratorUpdate","created":9,
"event":{"op":"remove","subject":"olaren","repo":"did:plc:scallop"}}`},
} {
t.Run(tt.name, func(t *testing.T) {
if _, err := decodeLegacy([]byte(tt.frame), nil); err == nil {
t.Error("frame decoded anyway")
}
})
}
}