package oltp
import "testing"
func str(s string) *string { return &s }
func flag(b bool) *bool { return &b }
// A patch writes the fields it names, leaves the rest alone, and
// records one row per field that actually changed -- with the before
// value read inside the same transaction as the write.
func TestPatchRepoWritesAndRecordsOnlyWhatChanged(t *testing.T) {
db := testDB(t)
if err := db.AddRepo("proj", t.TempDir(), "the original", Public); err != nil {
t.Fatal(err)
}
if err := db.SetRepoTopics("proj", "forge zit"); err != nil {
t.Fatal(err)
}
changes, err := db.PatchRepo("proj", RepoPatch{Description: str("rewritten")}, "dmestas")
if err != nil {
t.Fatal(err)
}
if len(changes) != 1 {
t.Fatalf("want one recorded change, got %d: %+v", len(changes), changes)
}
c := changes[0]
if c.Field != "description" || c.Was != "the original" || c.Now != "rewritten" {
t.Errorf("change = %+v, want description the original -> rewritten", c)
}
if c.Actor != "dmestas" {
t.Errorf("actor = %q, want dmestas: the log cannot answer WHO", c.Actor)
}
r, ok, err := db.RepoByName("proj")
if err != nil || !ok {
t.Fatalf("RepoByName: ok=%v err=%v", ok, err)
}
if r.Description != "rewritten" {
t.Errorf("description = %q: the named field was not written", r.Description)
}
if r.Topics != "forge zit" {
t.Errorf("topics = %q: a description-only patch rewrote another column", r.Topics)
}
logged, err := db.SettingsLog("proj", 10)
if err != nil {
t.Fatal(err)
}
if len(logged) != 1 || logged[0].Field != "description" || logged[0].Was != "the original" {
t.Errorf("SettingsLog = %+v, want the one description change", logged)
}
if logged[0].At == "" {
t.Error("the log row carries no timestamp, so it cannot be lined up against anything")
}
}
// Setting a field to the value it already holds is not a change: no
// write, no log row. Only the transaction can know that.
func TestPatchRepoIgnoresANoOp(t *testing.T) {
db := testDB(t)
if err := db.AddRepo("proj", t.TempDir(), "same", Public); err != nil {
t.Fatal(err)
}
changes, err := db.PatchRepo("proj", RepoPatch{Description: str("same")}, "dmestas")
if err != nil {
t.Fatalf("a no-op patch must not be an error -- it arrived and changed nothing: %v", err)
}
if len(changes) != 0 {
t.Errorf("a no-op patch recorded %+v", changes)
}
logged, _ := db.SettingsLog("proj", 10)
if len(logged) != 0 {
t.Errorf("a no-op patch wrote an audit row: %+v", logged)
}
}
// Mirrorable is skipped for the WRITE while private and never for the
// read. The comparison is against the STORED flag: comparing against
// the effective answer is what laundered "false because private" into
// "false because asked" and left a public repository unclonable.
func TestPatchRepoSkipsMirrorableWhilePrivate(t *testing.T) {
db := testDB(t)
if err := db.AddRepo("vault", t.TempDir(), "", Private); err != nil {
t.Fatal(err)
}
changes, err := db.PatchRepo("vault",
RepoPatch{Mirrorable: flag(false), Description: str("still writable")}, "dmestas")
if err != nil {
t.Fatal(err)
}
for _, c := range changes {
if c.Field == "mirrorable" {
t.Error("mirrorable was written while the repository is private: a disabled " +
"checkbox's value is not the admin's intent")
}
}
r, _, _ := db.RepoByName("vault")
if !r.Mirrorable {
t.Error("the stored mirrorable flag was zeroed during a private detour, so the " +
"repository comes back public and unclonable")
}
if r.Description != "still writable" {
t.Errorf("description = %q: skipping mirrorable also skipped the rest of the patch",
r.Description)
}
}
// A patch against a repository with no row is refused, not reported as
// a successful no-op.
func TestPatchRepoRefusesAnUnknownRepository(t *testing.T) {
db := testDB(t)
if _, err := db.PatchRepo("ghost", RepoPatch{Description: str("x")}, "dmestas"); err == nil {
t.Error("a patch against an unregistered repository reported success")
}
}
// Taking a repository private still carries the RFC 0004 category
// strings with it -- that half writes a different table and runs after
// the commit, and dropping it would leave a private repository readable.
func TestPatchRepoCarriesVisibilityCategories(t *testing.T) {
db := testDB(t)
if err := db.AddRepo("proj", t.TempDir(), "", Public); err != nil {
t.Fatal(err)
}
if _, err := db.PatchRepo("proj", RepoPatch{Visibility: str(Private)}, "dmestas"); err != nil {
t.Fatal(err)
}
if got := db.VisibilityName("proj"); got != "Private" {
t.Errorf("visibility name = %q, want Private: the category half of the change was "+
"dropped, so the repository reads private but is not", got)
}
}