...

Source file src/github.com/google/go-github/v45/github/messages_test.go

Documentation: github.com/google/go-github/v45/github

     1  // Copyright 2016 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"net/http"
    14  	"net/url"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/google/go-cmp/cmp"
    19  )
    20  
    21  func TestMessageMAC_BadHashTypePrefix(t *testing.T) {
    22  	const signature = "bogus1=1234567"
    23  	if _, _, err := messageMAC(signature); err == nil {
    24  		t.Fatal("messageMAC returned nil; wanted error")
    25  	}
    26  }
    27  
    28  func TestValidatePayload(t *testing.T) {
    29  	const defaultBody = `{"yo":true}` // All tests below use the default request body and signature.
    30  	const defaultSignature = "sha1=126f2c800419c60137ce748d7672e77b65cf16d6"
    31  	secretKey := []byte("0123456789abcdef")
    32  	tests := []struct {
    33  		signature       string
    34  		signatureHeader string
    35  		eventID         string
    36  		event           string
    37  		wantEventID     string
    38  		wantEvent       string
    39  		wantPayload     string
    40  	}{
    41  		// The following tests generate expected errors:
    42  		{},                         // Missing signature
    43  		{signature: "yo"},          // Missing signature prefix
    44  		{signature: "sha1=yo"},     // Signature not hex string
    45  		{signature: "sha1=012345"}, // Invalid signature
    46  		// The following tests expect err=nil:
    47  		{
    48  			signature:   defaultSignature,
    49  			eventID:     "dead-beef",
    50  			event:       "ping",
    51  			wantEventID: "dead-beef",
    52  			wantEvent:   "ping",
    53  			wantPayload: defaultBody,
    54  		},
    55  		{
    56  			signature:   defaultSignature,
    57  			event:       "ping",
    58  			wantEvent:   "ping",
    59  			wantPayload: defaultBody,
    60  		},
    61  		{
    62  			signature:   "sha256=b1f8020f5b4cd42042f807dd939015c4a418bc1ff7f604dd55b0a19b5d953d9b",
    63  			event:       "ping",
    64  			wantEvent:   "ping",
    65  			wantPayload: defaultBody,
    66  		},
    67  		{
    68  			signature:       "sha256=b1f8020f5b4cd42042f807dd939015c4a418bc1ff7f604dd55b0a19b5d953d9b",
    69  			signatureHeader: SHA256SignatureHeader,
    70  			event:           "ping",
    71  			wantEvent:       "ping",
    72  			wantPayload:     defaultBody,
    73  		},
    74  		{
    75  			signature:   "sha512=8456767023c1195682e182a23b3f5d19150ecea598fde8cb85918f7281b16079471b1329f92b912c4d8bd7455cb159777db8f29608b20c7c87323ba65ae62e1f",
    76  			event:       "ping",
    77  			wantEvent:   "ping",
    78  			wantPayload: defaultBody,
    79  		},
    80  	}
    81  
    82  	for _, test := range tests {
    83  		buf := bytes.NewBufferString(defaultBody)
    84  		req, err := http.NewRequest("GET", "http://localhost/event", buf)
    85  		if err != nil {
    86  			t.Fatalf("NewRequest: %v", err)
    87  		}
    88  		if test.signature != "" {
    89  			if test.signatureHeader != "" {
    90  				req.Header.Set(test.signatureHeader, test.signature)
    91  			} else {
    92  				req.Header.Set(SHA1SignatureHeader, test.signature)
    93  			}
    94  		}
    95  		req.Header.Set("Content-Type", "application/json")
    96  
    97  		got, err := ValidatePayload(req, secretKey)
    98  		if err != nil {
    99  			if test.wantPayload != "" {
   100  				t.Errorf("ValidatePayload(%#v): err = %v, want nil", test, err)
   101  			}
   102  			continue
   103  		}
   104  		if string(got) != test.wantPayload {
   105  			t.Errorf("ValidatePayload = %q, want %q", got, test.wantPayload)
   106  		}
   107  	}
   108  }
   109  
   110  func TestValidatePayload_FormGet(t *testing.T) {
   111  	payload := `{"yo":true}`
   112  	signature := "sha1=3374ef144403e8035423b23b02e2c9d7a4c50368"
   113  	secretKey := []byte("0123456789abcdef")
   114  
   115  	form := url.Values{}
   116  	form.Add("payload", payload)
   117  	req, err := http.NewRequest("POST", "http://localhost/event", strings.NewReader(form.Encode()))
   118  	if err != nil {
   119  		t.Fatalf("NewRequest: %v", err)
   120  	}
   121  	req.PostForm = form
   122  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   123  	req.Header.Set(SHA1SignatureHeader, signature)
   124  
   125  	got, err := ValidatePayload(req, secretKey)
   126  	if err != nil {
   127  		t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err)
   128  	}
   129  	if string(got) != payload {
   130  		t.Errorf("ValidatePayload = %q, want %q", got, payload)
   131  	}
   132  
   133  	// check that if payload is invalid we get error
   134  	req.Header.Set(SHA1SignatureHeader, "invalid signature")
   135  	if _, err = ValidatePayload(req, []byte{0}); err == nil {
   136  		t.Error("ValidatePayload = nil, want err")
   137  	}
   138  }
   139  
   140  func TestValidatePayload_FormPost(t *testing.T) {
   141  	payload := `{"yo":true}`
   142  	signature := "sha1=3374ef144403e8035423b23b02e2c9d7a4c50368"
   143  	secretKey := []byte("0123456789abcdef")
   144  
   145  	form := url.Values{}
   146  	form.Set("payload", payload)
   147  	buf := bytes.NewBufferString(form.Encode())
   148  	req, err := http.NewRequest("POST", "http://localhost/event", buf)
   149  	if err != nil {
   150  		t.Fatalf("NewRequest: %v", err)
   151  	}
   152  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   153  	req.Header.Set(SHA1SignatureHeader, signature)
   154  
   155  	got, err := ValidatePayload(req, secretKey)
   156  	if err != nil {
   157  		t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err)
   158  	}
   159  	if string(got) != payload {
   160  		t.Errorf("ValidatePayload = %q, want %q", got, payload)
   161  	}
   162  
   163  	// check that if payload is invalid we get error
   164  	req.Header.Set(SHA1SignatureHeader, "invalid signature")
   165  	if _, err = ValidatePayload(req, []byte{0}); err == nil {
   166  		t.Error("ValidatePayload = nil, want err")
   167  	}
   168  }
   169  
   170  func TestValidatePayload_InvalidContentType(t *testing.T) {
   171  	req, err := http.NewRequest("POST", "http://localhost/event", nil)
   172  	if err != nil {
   173  		t.Fatalf("NewRequest: %v", err)
   174  	}
   175  	req.Header.Set("Content-Type", "invalid content type")
   176  	if _, err = ValidatePayload(req, nil); err == nil {
   177  		t.Error("ValidatePayload = nil, want err")
   178  	}
   179  }
   180  
   181  func TestValidatePayload_NoSecretKey(t *testing.T) {
   182  	payload := `{"yo":true}`
   183  
   184  	form := url.Values{}
   185  	form.Set("payload", payload)
   186  	buf := bytes.NewBufferString(form.Encode())
   187  	req, err := http.NewRequest("POST", "http://localhost/event", buf)
   188  	if err != nil {
   189  		t.Fatalf("NewRequest: %v", err)
   190  	}
   191  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   192  
   193  	got, err := ValidatePayload(req, nil)
   194  	if err != nil {
   195  		t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err)
   196  	}
   197  	if string(got) != payload {
   198  		t.Errorf("ValidatePayload = %q, want %q", got, payload)
   199  	}
   200  }
   201  
   202  // badReader satisfies io.Reader but always returns an error.
   203  type badReader struct{}
   204  
   205  func (b *badReader) Read(p []byte) (int, error) {
   206  	return 0, errors.New("bad reader")
   207  }
   208  
   209  func (b *badReader) Close() error { return errors.New("bad reader") }
   210  
   211  func TestValidatePayload_BadRequestBody(t *testing.T) {
   212  	tests := []struct {
   213  		contentType string
   214  	}{
   215  		{contentType: "application/json"},
   216  		{contentType: "application/x-www-form-urlencoded"},
   217  	}
   218  
   219  	for i, tt := range tests {
   220  		t.Run(fmt.Sprintf("test #%v", i), func(t *testing.T) {
   221  			req := &http.Request{
   222  				Header: http.Header{"Content-Type": []string{tt.contentType}},
   223  				Body:   &badReader{},
   224  			}
   225  			if _, err := ValidatePayload(req, nil); err == nil {
   226  				t.Fatal("ValidatePayload returned nil; want error")
   227  			}
   228  		})
   229  	}
   230  }
   231  
   232  func TestValidatePayload_InvalidContentTypeParams(t *testing.T) {
   233  	req, err := http.NewRequest("POST", "http://localhost/event", nil)
   234  	if err != nil {
   235  		t.Fatalf("NewRequest: %v", err)
   236  	}
   237  	req.Header.Set("Content-Type", "application/json; charset=")
   238  	if _, err = ValidatePayload(req, nil); err == nil {
   239  		t.Error("ValidatePayload = nil, want err")
   240  	}
   241  }
   242  
   243  func TestValidatePayload_ValidContentTypeParams(t *testing.T) {
   244  	var requestBody = `{"yo":true}`
   245  	buf := bytes.NewBufferString(requestBody)
   246  
   247  	req, err := http.NewRequest("POST", "http://localhost/event", buf)
   248  	if err != nil {
   249  		t.Fatalf("NewRequest: %v", err)
   250  	}
   251  	req.Header.Set("Content-Type", "application/json; charset=UTF-8")
   252  
   253  	_, err = ValidatePayload(req, nil)
   254  	if err != nil {
   255  		t.Error("ValidatePayload = nil, want err")
   256  	}
   257  }
   258  
   259  func TestParseWebHook(t *testing.T) {
   260  	tests := []struct {
   261  		payload     interface{}
   262  		messageType string
   263  	}{
   264  		{
   265  			payload:     &BranchProtectionRuleEvent{},
   266  			messageType: "branch_protection_rule",
   267  		},
   268  		{
   269  			payload:     &CheckRunEvent{},
   270  			messageType: "check_run",
   271  		},
   272  		{
   273  			payload:     &CheckSuiteEvent{},
   274  			messageType: "check_suite",
   275  		},
   276  		{
   277  			payload:     &CommitCommentEvent{},
   278  			messageType: "commit_comment",
   279  		},
   280  		{
   281  			payload:     &ContentReferenceEvent{},
   282  			messageType: "content_reference",
   283  		},
   284  		{
   285  			payload:     &CreateEvent{},
   286  			messageType: "create",
   287  		},
   288  		{
   289  			payload:     &DeleteEvent{},
   290  			messageType: "delete",
   291  		},
   292  		{
   293  			payload:     &DeployKeyEvent{},
   294  			messageType: "deploy_key",
   295  		},
   296  		{
   297  			payload:     &DeploymentEvent{},
   298  			messageType: "deployment",
   299  		},
   300  		{
   301  			payload:     &DeploymentStatusEvent{},
   302  			messageType: "deployment_status",
   303  		},
   304  		{
   305  			payload:     &DiscussionEvent{},
   306  			messageType: "discussion",
   307  		},
   308  		{
   309  			payload:     &ForkEvent{},
   310  			messageType: "fork",
   311  		},
   312  		{
   313  			payload:     &GitHubAppAuthorizationEvent{},
   314  			messageType: "github_app_authorization",
   315  		},
   316  		{
   317  			payload:     &GollumEvent{},
   318  			messageType: "gollum",
   319  		},
   320  		{
   321  			payload:     &InstallationEvent{},
   322  			messageType: "installation",
   323  		},
   324  		{
   325  			payload:     &InstallationRepositoriesEvent{},
   326  			messageType: "installation_repositories",
   327  		},
   328  		{
   329  			payload:     &IssueCommentEvent{},
   330  			messageType: "issue_comment",
   331  		},
   332  		{
   333  			payload:     &IssuesEvent{},
   334  			messageType: "issues",
   335  		},
   336  		{
   337  			payload:     &LabelEvent{},
   338  			messageType: "label",
   339  		},
   340  		{
   341  			payload:     &MarketplacePurchaseEvent{},
   342  			messageType: "marketplace_purchase",
   343  		},
   344  		{
   345  			payload:     &MemberEvent{},
   346  			messageType: "member",
   347  		},
   348  		{
   349  			payload:     &MembershipEvent{},
   350  			messageType: "membership",
   351  		},
   352  		{
   353  			payload:     &MetaEvent{},
   354  			messageType: "meta",
   355  		},
   356  		{
   357  			payload:     &MilestoneEvent{},
   358  			messageType: "milestone",
   359  		},
   360  		{
   361  			payload:     &OrganizationEvent{},
   362  			messageType: "organization",
   363  		},
   364  		{
   365  			payload:     &OrgBlockEvent{},
   366  			messageType: "org_block",
   367  		},
   368  		{
   369  			payload:     &PackageEvent{},
   370  			messageType: "package",
   371  		},
   372  		{
   373  			payload:     &PageBuildEvent{},
   374  			messageType: "page_build",
   375  		},
   376  		{
   377  			payload:     &PingEvent{},
   378  			messageType: "ping",
   379  		},
   380  		{
   381  			payload:     &ProjectEvent{},
   382  			messageType: "project",
   383  		},
   384  		{
   385  			payload:     &ProjectCardEvent{},
   386  			messageType: "project_card",
   387  		},
   388  		{
   389  			payload:     &ProjectColumnEvent{},
   390  			messageType: "project_column",
   391  		},
   392  		{
   393  			payload:     &PublicEvent{},
   394  			messageType: "public",
   395  		},
   396  		{
   397  			payload:     &PullRequestEvent{},
   398  			messageType: "pull_request",
   399  		},
   400  		{
   401  			payload:     &PullRequestReviewEvent{},
   402  			messageType: "pull_request_review",
   403  		},
   404  		{
   405  			payload:     &PullRequestReviewCommentEvent{},
   406  			messageType: "pull_request_review_comment",
   407  		},
   408  		{
   409  			payload:     &PullRequestReviewThreadEvent{},
   410  			messageType: "pull_request_review_thread",
   411  		},
   412  		{
   413  			payload:     &PullRequestTargetEvent{},
   414  			messageType: "pull_request_target",
   415  		},
   416  		{
   417  			payload:     &PushEvent{},
   418  			messageType: "push",
   419  		},
   420  		{
   421  			payload:     &ReleaseEvent{},
   422  			messageType: "release",
   423  		},
   424  		{
   425  			payload:     &RepositoryEvent{},
   426  			messageType: "repository",
   427  		},
   428  		{
   429  			payload:     &RepositoryVulnerabilityAlertEvent{},
   430  			messageType: "repository_vulnerability_alert",
   431  		},
   432  		{
   433  			payload:     &SecretScanningAlertEvent{},
   434  			messageType: "secret_scanning_alert",
   435  		},
   436  		{
   437  			payload:     &StarEvent{},
   438  			messageType: "star",
   439  		},
   440  		{
   441  			payload:     &StatusEvent{},
   442  			messageType: "status",
   443  		},
   444  		{
   445  			payload:     &TeamEvent{},
   446  			messageType: "team",
   447  		},
   448  		{
   449  			payload:     &TeamAddEvent{},
   450  			messageType: "team_add",
   451  		},
   452  		{
   453  			payload:     &UserEvent{},
   454  			messageType: "user",
   455  		},
   456  		{
   457  			payload:     &WatchEvent{},
   458  			messageType: "watch",
   459  		},
   460  		{
   461  			payload:     &RepositoryImportEvent{},
   462  			messageType: "repository_import",
   463  		},
   464  		{
   465  			payload:     &RepositoryDispatchEvent{},
   466  			messageType: "repository_dispatch",
   467  		},
   468  		{
   469  			payload:     &WorkflowDispatchEvent{},
   470  			messageType: "workflow_dispatch",
   471  		},
   472  		{
   473  			payload:     &WorkflowJobEvent{},
   474  			messageType: "workflow_job",
   475  		},
   476  		{
   477  			payload:     &WorkflowRunEvent{},
   478  			messageType: "workflow_run",
   479  		},
   480  	}
   481  
   482  	for _, test := range tests {
   483  		p, err := json.Marshal(test.payload)
   484  		if err != nil {
   485  			t.Fatalf("Marshal(%#v): %v", test.payload, err)
   486  		}
   487  		got, err := ParseWebHook(test.messageType, p)
   488  		if err != nil {
   489  			t.Fatalf("ParseWebHook: %v", err)
   490  		}
   491  		if want := test.payload; !cmp.Equal(got, want) {
   492  			t.Errorf("ParseWebHook(%#v, %#v) = %#v, want %#v", test.messageType, p, got, want)
   493  		}
   494  	}
   495  }
   496  
   497  func TestParseWebHook_BadMessageType(t *testing.T) {
   498  	if _, err := ParseWebHook("bogus message type", []byte("{}")); err == nil {
   499  		t.Fatal("ParseWebHook returned nil; wanted error")
   500  	}
   501  }
   502  
   503  func TestValidatePayloadFromBody_UnableToParseBody(t *testing.T) {
   504  	if _, err := ValidatePayloadFromBody("application/x-www-form-urlencoded", bytes.NewReader([]byte(`%`)), "sha1=", []byte{}); err == nil {
   505  		t.Errorf("ValidatePayloadFromBody returned nil; wanted error")
   506  	}
   507  }
   508  
   509  func TestValidatePayloadFromBody_UnsupportedContentType(t *testing.T) {
   510  	if _, err := ValidatePayloadFromBody("invalid", bytes.NewReader([]byte(`{}`)), "sha1=", []byte{}); err == nil {
   511  		t.Errorf("ValidatePayloadFromBody returned nil; wanted error")
   512  	}
   513  }
   514  
   515  func TestDeliveryID(t *testing.T) {
   516  	id := "8970a780-244e-11e7-91ca-da3aabcb9793"
   517  	req, err := http.NewRequest("POST", "http://localhost", nil)
   518  	if err != nil {
   519  		t.Fatalf("DeliveryID: %v", err)
   520  	}
   521  	req.Header.Set("X-Github-Delivery", id)
   522  
   523  	got := DeliveryID(req)
   524  	if got != id {
   525  		t.Errorf("DeliveryID(%#v) = %q, want %q", req, got, id)
   526  	}
   527  }
   528  
   529  func TestWebHookType(t *testing.T) {
   530  	want := "yo"
   531  	req := &http.Request{
   532  		Header: http.Header{EventTypeHeader: []string{want}},
   533  	}
   534  	if got := WebHookType(req); got != want {
   535  		t.Errorf("WebHookType = %q, want %q", got, want)
   536  	}
   537  }
   538  

View as plain text