maintner: process a github new issue mutation, start of tests

Change-Id: I92b9d5b8ddce6b48ede67e5d5ec97c1a07d17e06
Reviewed-on: https://go-review.googlesource.com/36901
Reviewed-by: Kevin Burke <kev@inburke.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/maintner/maintner.go b/maintner/maintner.go
index f342af7..973a629 100644
--- a/maintner/maintner.go
+++ b/maintner/maintner.go
@@ -23,7 +23,7 @@
 	// ... TODO
 
 	mu           sync.RWMutex
-	githubIssues map[githubRepo]map[int]*githubIssue // repo -> num -> issue
+	githubIssues map[githubRepo]map[int32]*githubIssue // repo -> num -> issue
 	githubUsers  map[int64]*githubUser
 }
 
@@ -34,8 +34,8 @@
 // githubUser represents a github user.
 // It is a subset of https://developer.github.com/v3/users/#get-a-single-user
 type githubUser struct {
-	ID       int64
-	Username string // what Github calls "Login"
+	ID    int64
+	Login string
 }
 
 // githubIssue represents a github issue.
@@ -88,8 +88,67 @@
 	// TODO: more...
 }
 
+func (c *Corpus) repoKey(owner, repo string) githubRepo {
+	if owner == "" || repo == "" {
+		return ""
+	}
+	// TODO: avoid garbage, use interned strings? profile later
+	// once we have gigabytes of mutation logs to slurp at
+	// start-up. (The same thing mattered for Camlistore start-up
+	// time at least)
+	return githubRepo(owner + "/" + repo)
+}
+
+func (c *Corpus) getGithubUser(pu *maintpb.GithubUser) *githubUser {
+	if pu == nil {
+		return nil
+	}
+	if u := c.githubUsers[pu.Id]; u != nil {
+		if pu.Login != "" && pu.Login != u.Login {
+			u.Login = pu.Login
+		}
+		return u
+	}
+	if c.githubUsers == nil {
+		c.githubUsers = make(map[int64]*githubUser)
+	}
+	u := &githubUser{
+		ID:    pu.Id,
+		Login: pu.Login,
+	}
+	c.githubUsers[pu.Id] = u
+	return u
+}
+
 func (c *Corpus) processGithubIssueMutation(m *maintpb.GithubIssueMutation) {
-	// TODO: ...
+	k := c.repoKey(m.Owner, m.Repo)
+	if k == "" {
+		// TODO: errors? return false? skip for now.
+		return
+	}
+	if m.Number == 0 {
+		return
+	}
+	issueMap, ok := c.githubIssues[k]
+	if !ok {
+		if c.githubIssues == nil {
+			c.githubIssues = make(map[githubRepo]map[int32]*githubIssue)
+		}
+		issueMap = make(map[int32]*githubIssue)
+		c.githubIssues[k] = issueMap
+	}
+	gi, ok := issueMap[m.Number]
+	if !ok {
+		gi = &githubIssue{
+			Number: m.Number,
+			User:   c.getGithubUser(m.User),
+		}
+		issueMap[m.Number] = gi
+	}
+	if m.Body != "" {
+		gi.Body = m.Body
+	}
+	// TODO: times, etc.
 }
 
 // PopulateFromServer populates the corpus from a maintnerd server.
diff --git a/maintner/maintner_test.go b/maintner/maintner_test.go
new file mode 100644
index 0000000..aeb671a
--- /dev/null
+++ b/maintner/maintner_test.go
@@ -0,0 +1,60 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package maintner
+
+import (
+	"reflect"
+	"testing"
+
+	"golang.org/x/build/maintner/maintpb"
+)
+
+type mutationTest struct {
+	corpus Corpus
+	want   Corpus
+}
+
+func (mt mutationTest) test(t *testing.T, muts ...*maintpb.Mutation) {
+	c := mt.corpus
+	for _, m := range muts {
+		c.processMutationLocked(m)
+	}
+	if !reflect.DeepEqual(c, mt.want) {
+		t.Errorf("corpus mismatch\n got: %#v\nwant: %#v", c, mt.want)
+	}
+}
+
+func TestProcessMutation_Github_NewIssue(t *testing.T) {
+	mutationTest{
+		want: Corpus{
+			githubUsers: map[int64]*githubUser{
+				100: &githubUser{
+					Login: "gopherbot",
+					ID:    100,
+				},
+			},
+			githubIssues: map[githubRepo]map[int32]*githubIssue{
+				"golang/go": map[int32]*githubIssue{
+					3: &githubIssue{
+						Number: 3,
+						User:   &githubUser{ID: 100, Login: "gopherbot"},
+						Body:   "some body",
+					},
+				},
+			},
+		},
+	}.test(t, &maintpb.Mutation{
+		GithubIssue: &maintpb.GithubIssueMutation{
+			Owner:  "golang",
+			Repo:   "go",
+			Number: 3,
+			User: &maintpb.GithubUser{
+				Login: "gopherbot",
+				Id:    100,
+			},
+			Body: "some body",
+		},
+	})
+}