package dehub import ( "bytes" "dehub/accessctl" "dehub/sigcred" "io" "math/rand" "path/filepath" "runtime/debug" "testing" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" yaml "gopkg.in/yaml.v2" ) type harness struct { t *testing.T rand *rand.Rand repo *Repo cfg *Config sig sigcred.SignifierInterface } func newHarness(t *testing.T) *harness { rand := rand.New(rand.NewSource(0xb4eadb01)) sig, pubKeyBody := sigcred.SignifierPGPTmp(rand) pubKeyPath := filepath.Join(DehubDir, "root.asc") cfg := &Config{ Accounts: []Account{{ ID: "root", Signifiers: []sigcred.Signifier{{PGPPublicKeyFile: &sigcred.SignifierPGPFile{ Path: pubKeyPath, }}}, }}, AccessControls: []accessctl.BranchAccessControl{ { BranchPattern: "**", ChangeAccessControls: []accessctl.ChangeAccessControl{ { FilePathPattern: "**", Condition: accessctl.Condition{ Signature: &accessctl.ConditionSignature{ AccountIDs: []string{"root"}, Count: "100%", }, }, }, }, }, }, } cfgBody, err := yaml.Marshal(cfg) if err != nil { t.Fatal(err) } h := &harness{ t: t, rand: rand, repo: InitMemRepo(), cfg: cfg, sig: sig, } h.stage(map[string]string{ ConfigPath: string(cfgBody), pubKeyPath: string(pubKeyBody), }) return h } func (h *harness) stage(tree map[string]string) { w, err := h.repo.GitRepo.Worktree() if err != nil { h.t.Fatal(err) } fs := w.Filesystem for path, content := range tree { if content == "" { if _, err := w.Remove(path); err != nil { h.t.Fatalf("error removing %q: %v", path, err) } continue } dir := filepath.Dir(path) if err := fs.MkdirAll(dir, 0666); err != nil { h.t.Fatalf("error making directory %q: %v", dir, err) } f, err := fs.Create(path) if err != nil { h.t.Fatalf("error creating file %q: %v", path, err) } else if _, err := io.Copy(f, bytes.NewBufferString(content)); err != nil { h.t.Fatalf("error writing to file %q: %v", path, err) } else if err := f.Close(); err != nil { h.t.Fatalf("error closing file %q: %v", path, err) } else if _, err := w.Add(path); err != nil { h.t.Fatalf("error adding file %q to index: %v", path, err) } } } func (h *harness) changeCommit(msg, accountID string, sig sigcred.SignifierInterface) (Commit, plumbing.Hash) { commit, err := h.repo.NewCommitChange(msg) if err != nil { h.t.Fatalf("failed to create CommitChange: %v", err) } if sig != nil { if commit, err = h.repo.AccreditCommit(commit, accountID, sig); err != nil { h.t.Fatalf("failed to accredit commit: %v", err) } } hash, err := h.repo.Commit(commit, accountID) if err != nil { h.t.Fatalf("failed to commit ChangeCommit: %v", err) } return commit, hash } func (h *harness) reset(to plumbing.Hash, mode git.ResetMode) { w, err := h.repo.GitRepo.Worktree() if err != nil { h.t.Fatal(err) } err = w.Reset(&git.ResetOptions{ Commit: to, Mode: mode, }) if err != nil { h.t.Fatal(err) } } func TestHasStagedChanges(t *testing.T) { harness := newHarness(t) assertHasStaged := func(expHasStaged bool) { hasStaged, err := harness.repo.HasStagedChanges() if err != nil { debug.PrintStack() t.Fatalf("error calling HasStagedChanges: %v", err) } else if hasStaged != expHasStaged { debug.PrintStack() t.Fatalf("expected HasStagedChanges to return %v", expHasStaged) } } // the harness starts with some staged changes assertHasStaged(true) harness.stage(map[string]string{"foo": "bar"}) assertHasStaged(true) harness.changeCommit("first commit", "root", harness.sig) assertHasStaged(false) harness.stage(map[string]string{"foo": ""}) // delete foo assertHasStaged(true) harness.changeCommit("second commit", "root", harness.sig) assertHasStaged(false) } // TestOldConfig tests that having an older, now malformed, Config doesn't mess // with the current parsing, as long as the default access controls still work. func TestOldConfig(t *testing.T) { harness := newHarness(t) // overwrite the currently staged config file with an older form harness.stage(map[string]string{ConfigPath: ` --- accounts: - id: root signifiers: - type: pgp_public_key_file path: ".dehub/root.asc" access_controls: - pattern: "**" condition: type: signature account_ids: - root count: 0 `}) _, hash0 := harness.changeCommit("first commit, this is going great", "root", harness.sig) // even though that access_controls doesn't actually require any signatures, // it should be used because it's not well formed. harness.stage(map[string]string{"foo": "no rules!"}) _, hash1 := harness.changeCommit("ain't no laws", "toot", nil) // verifying the first should work, but not the second. if err := harness.repo.VerifyCommit(MainRefName, hash0); err != nil { t.Fatalf("first commit %q should be verifiable, but got: %v", hash0, err) } else if err := harness.repo.VerifyCommit(MainRefName, hash1); err == nil { t.Fatalf("second commit %q should not have been verified", hash1) } // reset back to hash0 harness.reset(hash0, git.HardReset) // make a commit fixing the config. everything should still be fine. harness.stage(map[string]string{ConfigPath: ` --- accounts: - id: root signifiers: - type: pgp_public_key_file path: ".dehub/root.asc" `}) _, hash2 := harness.changeCommit("Fix the config!", "root", harness.sig) if err := harness.repo.VerifyCommit(MainRefName, hash2); err != nil { t.Fatalf("config fix commit %q should be verifiable, but got: %v", hash2, err) } } // TestThisRepoStillVerifies opens this actual repository and ensures that all // commits in it still verify, given this codebase. func TestThisRepoStillVerifies(t *testing.T) { repo, err := OpenRepo(".") if err != nil { t.Fatalf("error opening repo: %v", err) } headCommit, _, err := repo.head() if err != nil { t.Fatalf("error getting repo head: %v", err) } checkedOutBranch, err := repo.CheckedOutBranch() if err != nil { t.Fatalf("error determining checked out branch: %v", err) } for _, hash := range headCommit.ParentHashes { if err := repo.VerifyCommit(checkedOutBranch, hash); err != nil { t.Fatalf("error verifying commit %q of branch %q: %v", hash, checkedOutBranch, err) } } }