// Copyright 2013 The go-github 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 github

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"golang.org/x/crypto/openpgp"
)

func TestRepositoriesService_ListCommits(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	// given
	mux.HandleFunc("/repos/o/r/commits", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r,
			values{
				"sha":    "s",
				"path":   "p",
				"author": "a",
				"since":  "2013-08-01T00:00:00Z",
				"until":  "2013-09-03T00:00:00Z",
			})
		fmt.Fprintf(w, `[{"sha": "s"}]`)
	})

	opt := &CommitsListOptions{
		SHA:    "s",
		Path:   "p",
		Author: "a",
		Since:  time.Date(2013, time.August, 1, 0, 0, 0, 0, time.UTC),
		Until:  time.Date(2013, time.September, 3, 0, 0, 0, 0, time.UTC),
	}
	ctx := context.Background()
	commits, _, err := client.Repositories.ListCommits(ctx, "o", "r", opt)
	if err != nil {
		t.Errorf("Repositories.ListCommits returned error: %v", err)
	}

	want := []*RepositoryCommit{{SHA: String("s")}}
	if !cmp.Equal(commits, want) {
		t.Errorf("Repositories.ListCommits returned %+v, want %+v", commits, want)
	}

	const methodName = "ListCommits"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.ListCommits(ctx, "\n", "\n", opt)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.ListCommits(ctx, "o", "r", opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_GetCommit(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"per_page": "2", "page": "2"})
		fmt.Fprintf(w, `{
		  "sha": "s",
		  "commit": { "message": "m" },
		  "author": { "login": "l" },
		  "committer": { "login": "l" },
		  "parents": [ { "sha": "s" } ],
		  "stats": { "additions": 104, "deletions": 4, "total": 108 },
		  "files": [
		    {
		      "filename": "f",
		      "additions": 10,
		      "deletions": 2,
		      "changes": 12,
		      "status": "s",
		      "patch": "p",
		      "blob_url": "b",
		      "raw_url": "r",
		      "contents_url": "c"
		    }
		  ]
		}`)
	})

	opts := &ListOptions{Page: 2, PerPage: 2}
	ctx := context.Background()
	commit, _, err := client.Repositories.GetCommit(ctx, "o", "r", "s", opts)
	if err != nil {
		t.Errorf("Repositories.GetCommit returned error: %v", err)
	}

	want := &RepositoryCommit{
		SHA: String("s"),
		Commit: &Commit{
			Message: String("m"),
		},
		Author: &User{
			Login: String("l"),
		},
		Committer: &User{
			Login: String("l"),
		},
		Parents: []*Commit{
			{
				SHA: String("s"),
			},
		},
		Stats: &CommitStats{
			Additions: Int(104),
			Deletions: Int(4),
			Total:     Int(108),
		},
		Files: []*CommitFile{
			{
				Filename:    String("f"),
				Additions:   Int(10),
				Deletions:   Int(2),
				Changes:     Int(12),
				Status:      String("s"),
				Patch:       String("p"),
				BlobURL:     String("b"),
				RawURL:      String("r"),
				ContentsURL: String("c"),
			},
		},
	}
	if !cmp.Equal(commit, want) {
		t.Errorf("Repositories.GetCommit returned \n%+v, want \n%+v", commit, want)
	}

	const methodName = "GetCommit"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.GetCommit(ctx, "\n", "\n", "\n", opts)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.GetCommit(ctx, "o", "r", "s", opts)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_GetCommitRaw_diff(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	const rawStr = "@@diff content"

	mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3Diff)
		fmt.Fprint(w, rawStr)
	})

	ctx := context.Background()
	got, _, err := client.Repositories.GetCommitRaw(ctx, "o", "r", "s", RawOptions{Type: Diff})
	if err != nil {
		t.Fatalf("Repositories.GetCommitRaw returned error: %v", err)
	}
	want := rawStr
	if got != want {
		t.Errorf("Repositories.GetCommitRaw returned %s want %s", got, want)
	}

	const methodName = "GetCommitRaw"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.GetCommitRaw(ctx, "\n", "\n", "\n", RawOptions{Type: Diff})
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.GetCommitRaw(ctx, "o", "r", "s", RawOptions{Type: Diff})
		if got != "" {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want ''", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_GetCommitRaw_patch(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	const rawStr = "@@patch content"

	mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3Patch)
		fmt.Fprint(w, rawStr)
	})

	ctx := context.Background()
	got, _, err := client.Repositories.GetCommitRaw(ctx, "o", "r", "s", RawOptions{Type: Patch})
	if err != nil {
		t.Fatalf("Repositories.GetCommitRaw returned error: %v", err)
	}
	want := rawStr
	if got != want {
		t.Errorf("Repositories.GetCommitRaw returned %s want %s", got, want)
	}
}

func TestRepositoriesService_GetCommitRaw_invalid(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.GetCommitRaw(ctx, "o", "r", "s", RawOptions{100})
	if err == nil {
		t.Fatal("Repositories.GetCommitRaw should return error")
	}
	if !strings.Contains(err.Error(), "unsupported raw type") {
		t.Error("Repositories.GetCommitRaw should return unsupported raw type error")
	}
}

func TestRepositoriesService_GetCommitSHA1(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()
	const sha1 = "01234abcde"

	mux.HandleFunc("/repos/o/r/commits/master", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3SHA)

		fmt.Fprintf(w, sha1)
	})

	ctx := context.Background()
	got, _, err := client.Repositories.GetCommitSHA1(ctx, "o", "r", "master", "")
	if err != nil {
		t.Errorf("Repositories.GetCommitSHA1 returned error: %v", err)
	}

	want := sha1
	if got != want {
		t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want)
	}

	mux.HandleFunc("/repos/o/r/commits/tag", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3SHA)
		testHeader(t, r, "If-None-Match", `"`+sha1+`"`)

		w.WriteHeader(http.StatusNotModified)
	})

	got, _, err = client.Repositories.GetCommitSHA1(ctx, "o", "r", "tag", sha1)
	if err == nil {
		t.Errorf("Expected HTTP 304 response")
	}

	want = ""
	if got != want {
		t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want)
	}

	const methodName = "GetCommitSHA1"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.GetCommitSHA1(ctx, "\n", "\n", "\n", "\n")
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.GetCommitSHA1(ctx, "o", "r", "master", "")
		if got != "" {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want ''", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_NonAlphabetCharacter_GetCommitSHA1(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()
	const sha1 = "01234abcde"

	mux.HandleFunc("/repos/o/r/commits/master%20hash", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3SHA)

		fmt.Fprintf(w, sha1)
	})

	ctx := context.Background()
	got, _, err := client.Repositories.GetCommitSHA1(ctx, "o", "r", "master%20hash", "")
	if err != nil {
		t.Errorf("Repositories.GetCommitSHA1 returned error: %v", err)
	}

	if want := sha1; got != want {
		t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want)
	}

	mux.HandleFunc("/repos/o/r/commits/tag", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3SHA)
		testHeader(t, r, "If-None-Match", `"`+sha1+`"`)

		w.WriteHeader(http.StatusNotModified)
	})

	got, _, err = client.Repositories.GetCommitSHA1(ctx, "o", "r", "tag", sha1)
	if err == nil {
		t.Errorf("Expected HTTP 304 response")
	}

	if want := ""; got != want {
		t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want)
	}
}

func TestRepositoriesService_TrailingPercent_GetCommitSHA1(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()
	const sha1 = "01234abcde"

	mux.HandleFunc("/repos/o/r/commits/comm%", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3SHA)

		fmt.Fprintf(w, sha1)
	})

	ctx := context.Background()
	got, _, err := client.Repositories.GetCommitSHA1(ctx, "o", "r", "comm%", "")
	if err != nil {
		t.Errorf("Repositories.GetCommitSHA1 returned error: %v", err)
	}

	if want := sha1; got != want {
		t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want)
	}

	mux.HandleFunc("/repos/o/r/commits/tag", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testHeader(t, r, "Accept", mediaTypeV3SHA)
		testHeader(t, r, "If-None-Match", `"`+sha1+`"`)

		w.WriteHeader(http.StatusNotModified)
	})

	got, _, err = client.Repositories.GetCommitSHA1(ctx, "o", "r", "tag", sha1)
	if err == nil {
		t.Errorf("Expected HTTP 304 response")
	}

	if want := ""; got != want {
		t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want)
	}
}

func TestRepositoriesService_CompareCommits(t *testing.T) {
	testCases := []struct {
		base string
		head string
	}{
		{base: "b", head: "h"},
		{base: "123base", head: "head123"},
		{base: "`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+123base", head: "head123`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+"},
	}

	for _, sample := range testCases {
		client, mux, _, teardown := setup()

		base := sample.base
		head := sample.head
		escapedBase := url.QueryEscape(base)
		escapedHead := url.QueryEscape(head)

		pattern := fmt.Sprintf("/repos/o/r/compare/%v...%v", base, head)

		mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
			testMethod(t, r, "GET")
			testFormValues(t, r, values{"per_page": "2", "page": "2"})
			fmt.Fprintf(w, `{
		  "base_commit": {
		    "sha": "s",
		    "commit": {
		      "author": { "name": "n" },
		      "committer": { "name": "n" },
		      "message": "m",
		      "tree": { "sha": "t" }
		    },
		    "author": { "login": "l" },
		    "committer": { "login": "l" },
		    "parents": [ { "sha": "s" } ]
		  },
		  "status": "s",
		  "ahead_by": 1,
		  "behind_by": 2,
		  "total_commits": 1,
		  "commits": [
		    {
		      "sha": "s",
		      "commit": { "author": { "name": "n" } },
		      "author": { "login": "l" },
		      "committer": { "login": "l" },
		      "parents": [ { "sha": "s" } ]
		    }
		  ],
		  "files": [ { "filename": "f" } ],
		  "html_url":      "https://github.com/o/r/compare/%[1]v...%[2]v",
		  "permalink_url": "https://github.com/o/r/compare/o:bbcd538c8e72b8c175046e27cc8f907076331401...o:0328041d1152db8ae77652d1618a02e57f745f17",
		  "diff_url":      "https://github.com/o/r/compare/%[1]v...%[2]v.diff",
		  "patch_url":     "https://github.com/o/r/compare/%[1]v...%[2]v.patch",
		  "url":           "https://api.github.com/repos/o/r/compare/%[1]v...%[2]v"
		}`, escapedBase, escapedHead)
		})

		opts := &ListOptions{Page: 2, PerPage: 2}
		ctx := context.Background()
		got, _, err := client.Repositories.CompareCommits(ctx, "o", "r", base, head, opts)
		if err != nil {
			t.Errorf("Repositories.CompareCommits returned error: %v", err)
		}

		want := &CommitsComparison{
			BaseCommit: &RepositoryCommit{
				SHA: String("s"),
				Commit: &Commit{
					Author:    &CommitAuthor{Name: String("n")},
					Committer: &CommitAuthor{Name: String("n")},
					Message:   String("m"),
					Tree:      &Tree{SHA: String("t")},
				},
				Author:    &User{Login: String("l")},
				Committer: &User{Login: String("l")},
				Parents: []*Commit{
					{
						SHA: String("s"),
					},
				},
			},
			Status:       String("s"),
			AheadBy:      Int(1),
			BehindBy:     Int(2),
			TotalCommits: Int(1),
			Commits: []*RepositoryCommit{
				{
					SHA: String("s"),
					Commit: &Commit{
						Author: &CommitAuthor{Name: String("n")},
					},
					Author:    &User{Login: String("l")},
					Committer: &User{Login: String("l")},
					Parents: []*Commit{
						{
							SHA: String("s"),
						},
					},
				},
			},
			Files: []*CommitFile{
				{
					Filename: String("f"),
				},
			},
			HTMLURL:      String(fmt.Sprintf("https://github.com/o/r/compare/%v...%v", escapedBase, escapedHead)),
			PermalinkURL: String("https://github.com/o/r/compare/o:bbcd538c8e72b8c175046e27cc8f907076331401...o:0328041d1152db8ae77652d1618a02e57f745f17"),
			DiffURL:      String(fmt.Sprintf("https://github.com/o/r/compare/%v...%v.diff", escapedBase, escapedHead)),
			PatchURL:     String(fmt.Sprintf("https://github.com/o/r/compare/%v...%v.patch", escapedBase, escapedHead)),
			URL:          String(fmt.Sprintf("https://api.github.com/repos/o/r/compare/%v...%v", escapedBase, escapedHead)),
		}

		if !cmp.Equal(got, want) {
			t.Errorf("Repositories.CompareCommits returned \n%+v, want \n%+v", got, want)
		}

		const methodName = "CompareCommits"
		testBadOptions(t, methodName, func() (err error) {
			_, _, err = client.Repositories.CompareCommits(ctx, "\n", "\n", "\n", "\n", opts)
			return err
		})

		testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
			got, resp, err := client.Repositories.CompareCommits(ctx, "o", "r", base, head, opts)
			if got != nil {
				t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
			}
			return resp, err
		})

		teardown()
	}
}

func TestRepositoriesService_CompareCommitsRaw_diff(t *testing.T) {
	testCases := []struct {
		base string
		head string
	}{
		{base: "b", head: "h"},
		{base: "123base", head: "head123"},
		{base: "`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+123base", head: "head123`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+"},
	}

	for _, sample := range testCases {
		client, mux, _, teardown := setup()

		base := sample.base
		head := sample.head
		pattern := fmt.Sprintf("/repos/o/r/compare/%v...%v", base, head)
		const rawStr = "@@diff content"

		mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
			testMethod(t, r, "GET")
			testHeader(t, r, "Accept", mediaTypeV3Diff)
			fmt.Fprint(w, rawStr)
		})

		ctx := context.Background()
		got, _, err := client.Repositories.CompareCommitsRaw(ctx, "o", "r", base, head, RawOptions{Type: Diff})
		if err != nil {
			t.Fatalf("Repositories.GetCommitRaw returned error: %v", err)
		}
		want := rawStr
		if got != want {
			t.Errorf("Repositories.GetCommitRaw returned %s want %s", got, want)
		}

		const methodName = "CompareCommitsRaw"
		testBadOptions(t, methodName, func() (err error) {
			_, _, err = client.Repositories.CompareCommitsRaw(ctx, "\n", "\n", "\n", "\n", RawOptions{Type: Diff})
			return err
		})

		testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
			got, resp, err := client.Repositories.CompareCommitsRaw(ctx, "o", "r", base, head, RawOptions{Type: Diff})
			if got != "" {
				t.Errorf("testNewRequestAndDoFailure %v = %#v, want ''", methodName, got)
			}
			return resp, err
		})

		teardown()
	}
}

func TestRepositoriesService_CompareCommitsRaw_patch(t *testing.T) {
	testCases := []struct {
		base string
		head string
	}{
		{base: "b", head: "h"},
		{base: "123base", head: "head123"},
		{base: "`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+123base", head: "head123`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+"},
	}

	for _, sample := range testCases {
		client, mux, _, teardown := setup()

		base := sample.base
		head := sample.head
		pattern := fmt.Sprintf("/repos/o/r/compare/%v...%v", base, head)
		const rawStr = "@@patch content"

		mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
			testMethod(t, r, "GET")
			testHeader(t, r, "Accept", mediaTypeV3Patch)
			fmt.Fprint(w, rawStr)
		})

		ctx := context.Background()
		got, _, err := client.Repositories.CompareCommitsRaw(ctx, "o", "r", base, head, RawOptions{Type: Patch})
		if err != nil {
			t.Fatalf("Repositories.GetCommitRaw returned error: %v", err)
		}
		want := rawStr
		if got != want {
			t.Errorf("Repositories.GetCommitRaw returned %s want %s", got, want)
		}

		teardown()
	}
}

func TestRepositoriesService_CompareCommitsRaw_invalid(t *testing.T) {
	ctx := context.Background()

	testCases := []struct {
		base string
		head string
	}{
		{base: "b", head: "h"},
		{base: "123base", head: "head123"},
		{base: "`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+123base", head: "head123`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?/*-+"},
	}

	for _, sample := range testCases {
		client, _, _, teardown := setup()
		_, _, err := client.Repositories.CompareCommitsRaw(ctx, "o", "r", sample.base, sample.head, RawOptions{100})
		if err == nil {
			t.Fatal("Repositories.GetCommitRaw should return error")
		}
		if !strings.Contains(err.Error(), "unsupported raw type") {
			t.Error("Repositories.GetCommitRaw should return unsupported raw type error")
		}
		teardown()
	}
}

func TestRepositoriesService_ListBranchesHeadCommit(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/commits/s/branches-where-head", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprintf(w, `[{"name": "b","commit":{"sha":"2e90302801c870f17b6152327d9b9a03c8eca0e2","url":"https://api.github.com/repos/google/go-github/commits/2e90302801c870f17b6152327d9b9a03c8eca0e2"},"protected":true}]`)
	})

	ctx := context.Background()
	branches, _, err := client.Repositories.ListBranchesHeadCommit(ctx, "o", "r", "s")
	if err != nil {
		t.Errorf("Repositories.ListBranchesHeadCommit returned error: %v", err)
	}

	want := []*BranchCommit{
		{
			Name: String("b"),
			Commit: &Commit{
				SHA: String("2e90302801c870f17b6152327d9b9a03c8eca0e2"),
				URL: String("https://api.github.com/repos/google/go-github/commits/2e90302801c870f17b6152327d9b9a03c8eca0e2"),
			},
			Protected: Bool(true),
		},
	}
	if !cmp.Equal(branches, want) {
		t.Errorf("Repositories.ListBranchesHeadCommit returned %+v, want %+v", branches, want)
	}

	const methodName = "ListBranchesHeadCommit"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.ListBranchesHeadCommit(ctx, "\n", "\n", "\n")
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.ListBranchesHeadCommit(ctx, "o", "r", "s")
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestBranchCommit_Marshal(t *testing.T) {
	testJSONMarshal(t, &BranchCommit{}, "{}")

	r := &BranchCommit{
		Name: String("n"),
		Commit: &Commit{
			SHA: String("s"),
			Author: &CommitAuthor{
				Date:  &referenceTime,
				Name:  String("n"),
				Email: String("e"),
				Login: String("u"),
			},
			Committer: &CommitAuthor{
				Date:  &referenceTime,
				Name:  String("n"),
				Email: String("e"),
				Login: String("u"),
			},
			Message: String("m"),
			Tree: &Tree{
				SHA: String("s"),
				Entries: []*TreeEntry{{
					SHA:     String("s"),
					Path:    String("p"),
					Mode:    String("m"),
					Type:    String("t"),
					Size:    Int(1),
					Content: String("c"),
					URL:     String("u"),
				}},
				Truncated: Bool(false),
			},
			Parents: nil,
			Stats: &CommitStats{
				Additions: Int(1),
				Deletions: Int(1),
				Total:     Int(1),
			},
			HTMLURL: String("h"),
			URL:     String("u"),
			Verification: &SignatureVerification{
				Verified:  Bool(false),
				Reason:    String("r"),
				Signature: String("s"),
				Payload:   String("p"),
			},
			NodeID:       String("n"),
			CommentCount: Int(1),
			SigningKey:   &openpgp.Entity{},
		},
		Protected: Bool(false),
	}

	want := `{
		"name": "n",
		"commit": {
			"sha": "s",
			"author": {
				"date": ` + referenceTimeStr + `,
				"name": "n",
				"email": "e",
				"username": "u"
			},
			"committer": {
				"date": ` + referenceTimeStr + `,
				"name": "n",
				"email": "e",
				"username": "u"
			},
			"message": "m",
			"tree": {
				"sha": "s",
				"tree": [
					{
						"sha": "s",
						"path": "p",
						"mode": "m",
						"type": "t",
						"size": 1,
						"content": "c",
						"url": "u"
					}
				],
				"truncated": false
			},
			"stats": {
				"additions": 1,
				"deletions": 1,
				"total": 1
			},
			"html_url": "h",
			"url": "u",
			"verification": {
				"verified": false,
				"reason": "r",
				"signature": "s",
				"payload": "p"
			},
			"node_id": "n",
			"comment_count": 1
		},
		"protected": false
	}`

	testJSONMarshal(t, r, want)
}

func TestCommitsComparison_Marshal(t *testing.T) {
	testJSONMarshal(t, &CommitsComparison{}, "{}")

	r := &CommitsComparison{
		BaseCommit:      &RepositoryCommit{NodeID: String("nid")},
		MergeBaseCommit: &RepositoryCommit{NodeID: String("nid")},
		Status:          String("status"),
		AheadBy:         Int(1),
		BehindBy:        Int(1),
		TotalCommits:    Int(1),
		Commits: []*RepositoryCommit{
			{
				NodeID: String("nid"),
			},
		},
		Files: []*CommitFile{
			{
				SHA: String("sha"),
			},
		},
		HTMLURL:      String("hurl"),
		PermalinkURL: String("purl"),
		DiffURL:      String("durl"),
		PatchURL:     String("purl"),
		URL:          String("url"),
	}

	want := `{
		"base_commit": {
			"node_id": "nid"
		},
		"merge_base_commit": {
			"node_id": "nid"
		},
		"status": "status",
		"ahead_by": 1,
		"behind_by": 1,
		"total_commits": 1,
		"commits": [
			{
				"node_id": "nid"
			}
		],
		"files": [
			{
				"sha": "sha"
			}
		],
		"html_url": "hurl",
		"permalink_url": "purl",
		"diff_url": "durl",
		"patch_url": "purl",
		"url": "url"
	}`

	testJSONMarshal(t, r, want)
}

func TestCommitFile_Marshal(t *testing.T) {
	testJSONMarshal(t, &CommitFile{}, "{}")

	r := &CommitFile{
		SHA:              String("sha"),
		Filename:         String("fn"),
		Additions:        Int(1),
		Deletions:        Int(1),
		Changes:          Int(1),
		Status:           String("status"),
		Patch:            String("patch"),
		BlobURL:          String("burl"),
		RawURL:           String("rurl"),
		ContentsURL:      String("curl"),
		PreviousFilename: String("pf"),
	}

	want := `{
		"sha": "sha",
		"filename": "fn",
		"additions": 1,
		"deletions": 1,
		"changes": 1,
		"status": "status",
		"patch": "patch",
		"blob_url": "burl",
		"raw_url": "rurl",
		"contents_url": "curl",
		"previous_filename": "pf"
	}`

	testJSONMarshal(t, r, want)
}

func TestCommitStats_Marshal(t *testing.T) {
	testJSONMarshal(t, &CommitStats{}, "{}")

	r := &CommitStats{
		Additions: Int(1),
		Deletions: Int(1),
		Total:     Int(1),
	}

	want := `{
		"additions": 1,
		"deletions": 1,
		"total": 1
	}`

	testJSONMarshal(t, r, want)
}

func TestRepositoryCommit_Marshal(t *testing.T) {
	testJSONMarshal(t, &RepositoryCommit{}, "{}")

	r := &RepositoryCommit{
		NodeID: String("nid"),
		SHA:    String("sha"),
		Commit: &Commit{
			Message: String("m"),
		},
		Author: &User{
			Login: String("l"),
		},
		Committer: &User{
			Login: String("l"),
		},
		Parents: []*Commit{
			{
				SHA: String("s"),
			},
		},
		HTMLURL:     String("hurl"),
		URL:         String("url"),
		CommentsURL: String("curl"),
		Stats: &CommitStats{
			Additions: Int(104),
			Deletions: Int(4),
			Total:     Int(108),
		},
		Files: []*CommitFile{
			{
				Filename:    String("f"),
				Additions:   Int(10),
				Deletions:   Int(2),
				Changes:     Int(12),
				Status:      String("s"),
				Patch:       String("p"),
				BlobURL:     String("b"),
				RawURL:      String("r"),
				ContentsURL: String("c"),
			},
		},
	}

	want := `{
		"node_id": "nid",
		"sha": "sha",
		"commit": {
			"message": "m"
		},
		"author": {
			"login": "l"
		},
		"committer": {
			"login": "l"
		},
		"parents": [
			{
				"sha": "s"
			}
		],
		"html_url": "hurl",
		"url": "url",
		"comments_url": "curl",
		"stats": {
			"additions": 104,
			"deletions": 4,
			"total": 108
		},
		"files": [
			{
				"filename": "f",
				"additions": 10,
				"deletions": 2,
				"changes": 12,
				"status": "s",
				"patch": "p",
				"blob_url": "b",
				"raw_url": "r",
				"contents_url": "c"
			}
		]
	}`

	testJSONMarshal(t, r, want)
}
