github.com/google/go-github/v42@v42.0.0/github/code-scanning_test.go (about)

     1  // Copyright 2020 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  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"net/http"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  )
    18  
    19  func TestCodeScanningService_Alert_ID(t *testing.T) {
    20  	// Test: nil Alert ID == 0
    21  	var a *Alert
    22  	id := a.ID()
    23  	var want int64
    24  	if id != want {
    25  		t.Errorf("Alert.ID error returned %+v, want %+v", id, want)
    26  	}
    27  
    28  	// Test: Valid HTMLURL
    29  	a = &Alert{
    30  		HTMLURL: String("https://github.com/o/r/security/code-scanning/88"),
    31  	}
    32  	id = a.ID()
    33  	want = 88
    34  	if !cmp.Equal(id, want) {
    35  		t.Errorf("Alert.ID error returned %+v, want %+v", id, want)
    36  	}
    37  
    38  	// Test: HTMLURL is nil
    39  	a = &Alert{}
    40  	id = a.ID()
    41  	want = 0
    42  	if !cmp.Equal(id, want) {
    43  		t.Errorf("Alert.ID error returned %+v, want %+v", id, want)
    44  	}
    45  
    46  	// Test: ID can't be parsed as an int
    47  	a = &Alert{
    48  		HTMLURL: String("https://github.com/o/r/security/code-scanning/bad88"),
    49  	}
    50  	id = a.ID()
    51  	want = 0
    52  	if !cmp.Equal(id, want) {
    53  		t.Errorf("Alert.ID error returned %+v, want %+v", id, want)
    54  	}
    55  }
    56  
    57  func TestCodeScanningService_UploadSarif(t *testing.T) {
    58  	client, mux, _, teardown := setup()
    59  	defer teardown()
    60  
    61  	mux.HandleFunc("/repos/o/r/code-scanning/sarifs", func(w http.ResponseWriter, r *http.Request) {
    62  		v := new(SarifAnalysis)
    63  		json.NewDecoder(r.Body).Decode(v)
    64  		testMethod(t, r, "POST")
    65  		want := &SarifAnalysis{CommitSHA: String("abc"), Ref: String("ref/head/main"), Sarif: String("abc"), CheckoutURI: String("uri"), StartedAt: &Timestamp{time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC)}, ToolName: String("codeql-cli")}
    66  		if !cmp.Equal(v, want) {
    67  			t.Errorf("Request body = %+v, want %+v", v, want)
    68  		}
    69  
    70  		fmt.Fprint(w, `{"commit_sha":"abc","ref":"ref/head/main","sarif":"abc"}`)
    71  	})
    72  
    73  	ctx := context.Background()
    74  	sarifAnalysis := &SarifAnalysis{CommitSHA: String("abc"), Ref: String("ref/head/main"), Sarif: String("abc"), CheckoutURI: String("uri"), StartedAt: &Timestamp{time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC)}, ToolName: String("codeql-cli")}
    75  	_, _, err := client.CodeScanning.UploadSarif(ctx, "o", "r", sarifAnalysis)
    76  	if err != nil {
    77  		t.Errorf("CodeScanning.UploadSarif returned error: %v", err)
    78  	}
    79  
    80  	const methodName = "UploadSarif"
    81  	testBadOptions(t, methodName, func() (err error) {
    82  		_, _, err = client.CodeScanning.UploadSarif(ctx, "\n", "\n", sarifAnalysis)
    83  		return err
    84  	})
    85  
    86  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
    87  		_, resp, err := client.CodeScanning.UploadSarif(ctx, "o", "r", sarifAnalysis)
    88  		return resp, err
    89  	})
    90  }
    91  
    92  func TestCodeScanningService_ListAlertsForRepo(t *testing.T) {
    93  	client, mux, _, teardown := setup()
    94  	defer teardown()
    95  
    96  	mux.HandleFunc("/repos/o/r/code-scanning/alerts", func(w http.ResponseWriter, r *http.Request) {
    97  		testMethod(t, r, "GET")
    98  		testFormValues(t, r, values{"state": "open", "ref": "heads/master"})
    99  		fmt.Fprint(w, `[{
   100  				"rule_id":"js/trivial-conditional",
   101  				"rule_severity":"warning",
   102  				"rule_description":"Useless conditional",
   103  				"tool": {
   104  					"name": "CodeQL",
   105  					"guid": null,
   106  					"version": "1.4.0"
   107  				},
   108  				"rule": {
   109  					"id": "js/trivial-conditional",
   110  					"severity": "warning",
   111  					"description": "Useless conditional",
   112  					"name": "js/trivial-conditional",
   113  					"full_description": "Expression has no effect",
   114  					"help": "Expression has no effect"
   115  				},
   116  				"most_recent_instance": {
   117  					"ref": "refs/heads/main",
   118  					"state": "open",
   119  					"commit_sha": "abcdefg12345",
   120  					"message": {
   121  						"text": "This path depends on a user-provided value."
   122  					},
   123  					"location": {
   124  						"path": "spec-main/api-session-spec.ts",
   125  						"start_line": 917,
   126  						"end_line": 917,
   127  						"start_column": 7,
   128  						"end_column": 18
   129  					},
   130  					"classifications": [
   131  						"test"
   132  					]
   133  				},
   134  				"created_at":"2020-05-06T12:00:00Z",
   135  				"state":"open",
   136  				"closed_by":null,
   137  				"closed_at":null,
   138  				"url":"https://api.github.com/repos/o/r/code-scanning/alerts/25",
   139  				"html_url":"https://github.com/o/r/security/code-scanning/25"
   140  				},
   141  				{
   142  				"rule_id":"js/useless-expression",
   143  				"rule_severity":"warning",
   144  				"rule_description":"Expression has no effect",
   145  				"tool": {
   146  					"name": "CodeQL",
   147  					"guid": null,
   148  					"version": "1.4.0"
   149  				},
   150  				"rule": {
   151  					"id": "js/useless-expression",
   152  					"severity": "warning",
   153  					"description": "Expression has no effect",
   154  					"name": "js/useless-expression",
   155  					"full_description": "Expression has no effect",
   156  					"help": "Expression has no effect"
   157  				},
   158  				"most_recent_instance": {
   159  					"ref": "refs/heads/main",
   160  					"state": "open",
   161  					"commit_sha": "abcdefg12345",
   162  					"message": {
   163  						"text": "This path depends on a user-provided value."
   164  					},
   165  					"location": {
   166  						"path": "spec-main/api-session-spec.ts",
   167  						"start_line": 917,
   168  						"end_line": 917,
   169  						"start_column": 7,
   170  						"end_column": 18
   171  					},
   172  					"classifications": [
   173  						"test"
   174  					]
   175  				},
   176  				"created_at":"2020-05-06T12:00:00Z",
   177  				"state":"open",
   178  				"closed_by":null,
   179  				"closed_at":null,
   180  				"url":"https://api.github.com/repos/o/r/code-scanning/alerts/88",
   181  				"html_url":"https://github.com/o/r/security/code-scanning/88"
   182  				}]`)
   183  	})
   184  
   185  	opts := &AlertListOptions{State: "open", Ref: "heads/master"}
   186  	ctx := context.Background()
   187  	alerts, _, err := client.CodeScanning.ListAlertsForRepo(ctx, "o", "r", opts)
   188  	if err != nil {
   189  		t.Errorf("CodeScanning.ListAlertsForRepo returned error: %v", err)
   190  	}
   191  
   192  	date := Timestamp{time.Date(2020, time.May, 06, 12, 00, 00, 0, time.UTC)}
   193  	want := []*Alert{
   194  		{
   195  			RuleID:          String("js/trivial-conditional"),
   196  			RuleSeverity:    String("warning"),
   197  			RuleDescription: String("Useless conditional"),
   198  			Tool:            &Tool{Name: String("CodeQL"), GUID: nil, Version: String("1.4.0")},
   199  			Rule: &Rule{
   200  				ID:              String("js/trivial-conditional"),
   201  				Severity:        String("warning"),
   202  				Description:     String("Useless conditional"),
   203  				Name:            String("js/trivial-conditional"),
   204  				FullDescription: String("Expression has no effect"),
   205  				Help:            String("Expression has no effect"),
   206  			},
   207  			CreatedAt: &date,
   208  			State:     String("open"),
   209  			ClosedBy:  nil,
   210  			ClosedAt:  nil,
   211  			URL:       String("https://api.github.com/repos/o/r/code-scanning/alerts/25"),
   212  			HTMLURL:   String("https://github.com/o/r/security/code-scanning/25"),
   213  			MostRecentInstance: &MostRecentInstance{
   214  				Ref:       String("refs/heads/main"),
   215  				State:     String("open"),
   216  				CommitSHA: String("abcdefg12345"),
   217  				Message: &Message{
   218  					Text: String("This path depends on a user-provided value."),
   219  				},
   220  				Location: &Location{
   221  					Path:        String("spec-main/api-session-spec.ts"),
   222  					StartLine:   Int(917),
   223  					EndLine:     Int(917),
   224  					StartColumn: Int(7),
   225  					EndColumn:   Int(18),
   226  				},
   227  				Classifications: []string{"test"},
   228  			},
   229  		},
   230  		{
   231  			RuleID:          String("js/useless-expression"),
   232  			RuleSeverity:    String("warning"),
   233  			RuleDescription: String("Expression has no effect"),
   234  			Tool:            &Tool{Name: String("CodeQL"), GUID: nil, Version: String("1.4.0")},
   235  			Rule: &Rule{
   236  				ID:              String("js/useless-expression"),
   237  				Severity:        String("warning"),
   238  				Description:     String("Expression has no effect"),
   239  				Name:            String("js/useless-expression"),
   240  				FullDescription: String("Expression has no effect"),
   241  				Help:            String("Expression has no effect"),
   242  			},
   243  			CreatedAt: &date,
   244  			State:     String("open"),
   245  			ClosedBy:  nil,
   246  			ClosedAt:  nil,
   247  			URL:       String("https://api.github.com/repos/o/r/code-scanning/alerts/88"),
   248  			HTMLURL:   String("https://github.com/o/r/security/code-scanning/88"),
   249  			MostRecentInstance: &MostRecentInstance{
   250  				Ref:       String("refs/heads/main"),
   251  				State:     String("open"),
   252  				CommitSHA: String("abcdefg12345"),
   253  				Message: &Message{
   254  					Text: String("This path depends on a user-provided value."),
   255  				},
   256  				Location: &Location{
   257  					Path:        String("spec-main/api-session-spec.ts"),
   258  					StartLine:   Int(917),
   259  					EndLine:     Int(917),
   260  					StartColumn: Int(7),
   261  					EndColumn:   Int(18),
   262  				},
   263  				Classifications: []string{"test"},
   264  			},
   265  		},
   266  	}
   267  	if !cmp.Equal(alerts, want) {
   268  		t.Errorf("CodeScanning.ListAlertsForRepo returned %+v, want %+v", alerts, want)
   269  	}
   270  
   271  	const methodName = "ListAlertsForRepo"
   272  	testBadOptions(t, methodName, func() (err error) {
   273  		_, _, err = client.CodeScanning.ListAlertsForRepo(ctx, "\n", "\n", opts)
   274  		return err
   275  	})
   276  
   277  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   278  		got, resp, err := client.CodeScanning.ListAlertsForRepo(ctx, "o", "r", opts)
   279  		if got != nil {
   280  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   281  		}
   282  		return resp, err
   283  	})
   284  }
   285  
   286  func TestCodeScanningService_GetAlert(t *testing.T) {
   287  	client, mux, _, teardown := setup()
   288  	defer teardown()
   289  
   290  	mux.HandleFunc("/repos/o/r/code-scanning/alerts/88", func(w http.ResponseWriter, r *http.Request) {
   291  		testMethod(t, r, "GET")
   292  		fmt.Fprint(w, `{"rule_id":"js/useless-expression",
   293  				"rule_severity":"warning",
   294  				"rule_description":"Expression has no effect",
   295  				"tool": {
   296  					"name": "CodeQL",
   297  					"guid": null,
   298  					"version": "1.4.0"
   299  				},
   300  				"rule": {
   301  					"id": "useless expression",
   302  					"severity": "warning",
   303  					"description": "Expression has no effect",
   304  					"name": "useless expression",
   305  					"full_description": "Expression has no effect",
   306  					"help": "Expression has no effect"
   307  				},
   308  				"most_recent_instance": {
   309  					"ref": "refs/heads/main",
   310  					"state": "open",
   311  					"commit_sha": "abcdefg12345",
   312  					"message": {
   313  						"text": "This path depends on a user-provided value."
   314  					},
   315  					"location": {
   316  						"path": "spec-main/api-session-spec.ts",
   317  						"start_line": 917,
   318  						"end_line": 917,
   319  						"start_column": 7,
   320  						"end_column": 18
   321  					},
   322  					"classifications": [
   323  						"test"
   324  					]
   325  				},      
   326  				"created_at":"2019-01-02T15:04:05Z",
   327  				"state":"open",
   328  				"closed_by":null,
   329  				"closed_at":null,
   330  				"url":"https://api.github.com/repos/o/r/code-scanning/alerts/88",
   331  				"html_url":"https://github.com/o/r/security/code-scanning/88"}`)
   332  	})
   333  
   334  	ctx := context.Background()
   335  	alert, _, err := client.CodeScanning.GetAlert(ctx, "o", "r", 88)
   336  	if err != nil {
   337  		t.Errorf("CodeScanning.GetAlert returned error: %v", err)
   338  	}
   339  
   340  	date := Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}
   341  	want := &Alert{
   342  		RuleID:          String("js/useless-expression"),
   343  		RuleSeverity:    String("warning"),
   344  		RuleDescription: String("Expression has no effect"),
   345  		Tool:            &Tool{Name: String("CodeQL"), GUID: nil, Version: String("1.4.0")},
   346  		Rule: &Rule{
   347  			ID:              String("useless expression"),
   348  			Severity:        String("warning"),
   349  			Description:     String("Expression has no effect"),
   350  			Name:            String("useless expression"),
   351  			FullDescription: String("Expression has no effect"),
   352  			Help:            String("Expression has no effect"),
   353  		},
   354  		CreatedAt: &date,
   355  		State:     String("open"),
   356  		ClosedBy:  nil,
   357  		ClosedAt:  nil,
   358  		URL:       String("https://api.github.com/repos/o/r/code-scanning/alerts/88"),
   359  		HTMLURL:   String("https://github.com/o/r/security/code-scanning/88"),
   360  		MostRecentInstance: &MostRecentInstance{
   361  			Ref:       String("refs/heads/main"),
   362  			State:     String("open"),
   363  			CommitSHA: String("abcdefg12345"),
   364  			Message: &Message{
   365  				Text: String("This path depends on a user-provided value."),
   366  			},
   367  			Location: &Location{
   368  				Path:        String("spec-main/api-session-spec.ts"),
   369  				StartLine:   Int(917),
   370  				EndLine:     Int(917),
   371  				StartColumn: Int(7),
   372  				EndColumn:   Int(18),
   373  			},
   374  			Classifications: []string{"test"},
   375  		},
   376  	}
   377  	if !cmp.Equal(alert, want) {
   378  		t.Errorf("CodeScanning.GetAlert returned %+v, want %+v", alert, want)
   379  	}
   380  
   381  	const methodName = "GetAlert"
   382  	testBadOptions(t, methodName, func() (err error) {
   383  		_, _, err = client.CodeScanning.GetAlert(ctx, "\n", "\n", -88)
   384  		return err
   385  	})
   386  
   387  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   388  		got, resp, err := client.CodeScanning.GetAlert(ctx, "o", "r", 88)
   389  		if got != nil {
   390  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   391  		}
   392  		return resp, err
   393  	})
   394  }
   395  
   396  func TestAlert_Marshal(t *testing.T) {
   397  	testJSONMarshal(t, &Alert{}, "{}")
   398  
   399  	u := &Alert{
   400  		RuleID:          String("rid"),
   401  		RuleSeverity:    String("rs"),
   402  		RuleDescription: String("rd"),
   403  		Tool: &Tool{
   404  			Name:    String("n"),
   405  			GUID:    String("g"),
   406  			Version: String("v"),
   407  		},
   408  		CreatedAt: &Timestamp{referenceTime},
   409  		State:     String("fixed"),
   410  		ClosedBy: &User{
   411  			Login:     String("l"),
   412  			ID:        Int64(1),
   413  			NodeID:    String("n"),
   414  			URL:       String("u"),
   415  			ReposURL:  String("r"),
   416  			EventsURL: String("e"),
   417  			AvatarURL: String("a"),
   418  		},
   419  		ClosedAt: &Timestamp{referenceTime},
   420  		URL:      String("url"),
   421  		HTMLURL:  String("hurl"),
   422  	}
   423  
   424  	want := `{
   425  		"rule_id": "rid",
   426  		"rule_severity": "rs",
   427  		"rule_description": "rd",
   428  		"tool": {
   429  			"name": "n",
   430  			"guid": "g",
   431  			"version": "v"
   432  		},
   433  		"created_at": ` + referenceTimeStr + `,
   434  		"state": "fixed",
   435  		"closed_by": {
   436  			"login": "l",
   437  			"id": 1,
   438  			"node_id": "n",
   439  			"avatar_url": "a",
   440  			"url": "u",
   441  			"events_url": "e",
   442  			"repos_url": "r"
   443  		},
   444  		"closed_at": ` + referenceTimeStr + `,
   445  		"url": "url",
   446  		"html_url": "hurl"
   447  	}`
   448  
   449  	testJSONMarshal(t, u, want)
   450  }
   451  
   452  func TestLocation_Marshal(t *testing.T) {
   453  	testJSONMarshal(t, &Location{}, "{}")
   454  
   455  	u := &Location{
   456  		Path:        String("path"),
   457  		StartLine:   Int(1),
   458  		EndLine:     Int(2),
   459  		StartColumn: Int(3),
   460  		EndColumn:   Int(4),
   461  	}
   462  
   463  	want := `{
   464  		"path": "path",
   465  		"start_line": 1,
   466  		"end_line": 2,
   467  		"start_column": 3,
   468  		"end_column": 4
   469  	}`
   470  
   471  	testJSONMarshal(t, u, want)
   472  }
   473  
   474  func TestRule_Marshal(t *testing.T) {
   475  	testJSONMarshal(t, &Rule{}, "{}")
   476  
   477  	u := &Rule{
   478  		ID:                    String("1"),
   479  		Severity:              String("3"),
   480  		Description:           String("description"),
   481  		Name:                  String("first"),
   482  		SecuritySeverityLevel: String("2"),
   483  		FullDescription:       String("summary"),
   484  		Tags:                  []string{"tag1", "tag2"},
   485  		Help:                  String("Help Text"),
   486  	}
   487  
   488  	want := `{
   489  		"id":                      "1",
   490  		"severity":                "3",
   491  		"description":             "description",
   492  		"name":                    "first",
   493  		"security_severity_level": "2",
   494  		"full_description":        "summary",
   495  		"tags":                    ["tag1", "tag2"],
   496  		"help":                    "Help Text"
   497  	}`
   498  
   499  	testJSONMarshal(t, u, want)
   500  }
   501  
   502  func TestTool_Marshal(t *testing.T) {
   503  	testJSONMarshal(t, &Tool{}, "{}")
   504  
   505  	u := &Tool{
   506  		Name:    String("name"),
   507  		GUID:    String("guid"),
   508  		Version: String("ver"),
   509  	}
   510  
   511  	want := `{
   512  		"name": "name",
   513  		"guid": "guid",
   514  		"version": "ver"
   515  	}`
   516  
   517  	testJSONMarshal(t, u, want)
   518  }
   519  
   520  func TestMessage_Marshal(t *testing.T) {
   521  	testJSONMarshal(t, &Message{}, "{}")
   522  
   523  	u := &Message{
   524  		Text: String("text"),
   525  	}
   526  
   527  	want := `{
   528  		"text": "text"
   529  	}`
   530  
   531  	testJSONMarshal(t, u, want)
   532  }
   533  
   534  func TestCodeScanningService_ListAnalysesForRepo(t *testing.T) {
   535  	client, mux, _, teardown := setup()
   536  	defer teardown()
   537  
   538  	mux.HandleFunc("/repos/o/r/code-scanning/analyses", func(w http.ResponseWriter, r *http.Request) {
   539  		testMethod(t, r, "GET")
   540  		testFormValues(t, r, values{"sarif_id": "8981cd8e-b078-4ac3-a3be-1dad7dbd0b582", "ref": "heads/master"})
   541  		fmt.Fprint(w, `[
   542  			  {
   543  				"ref": "refs/heads/main",
   544  				"commit_sha": "d99612c3e1f2970085cfbaeadf8f010ef69bad83",
   545  				"analysis_key": ".github/workflows/codeql-analysis.yml:analyze",
   546  				"environment": "{\"language\":\"python\"}",
   547  				"error": "",
   548  				"category": ".github/workflows/codeql-analysis.yml:analyze/language:python",
   549  				"created_at": "2020-08-27T15:05:21Z",
   550  				"results_count": 17,
   551  				"rules_count": 49,
   552  				"id": 201,
   553  				"url": "https://api.github.com/repos/o/r/code-scanning/analyses/201",
   554  				"sarif_id": "8981cd8e-b078-4ac3-a3be-1dad7dbd0b582",
   555  				"tool": {
   556  				  "name": "CodeQL",
   557  				  "guid": null,
   558  				  "version": "2.4.0"
   559  				},
   560  				"deletable": true,
   561  				"warning": ""
   562  			  },
   563  			  {
   564  				"ref": "refs/heads/my-branch",
   565  				"commit_sha": "c8cff6510d4d084fb1b4aa13b64b97ca12b07321",
   566  				"analysis_key": ".github/workflows/shiftleft.yml:build",
   567  				"environment": "{}",
   568  				"error": "",
   569  				"category": ".github/workflows/shiftleft.yml:build/",
   570  				"created_at": "2020-08-27T15:05:21Z",
   571  				"results_count": 17,
   572  				"rules_count": 32,
   573  				"id": 200,
   574  				"url": "https://api.github.com/repos/o/r/code-scanning/analyses/200",
   575  				"sarif_id": "8981cd8e-b078-4ac3-a3be-1dad7dbd0b582",
   576  				"tool": {
   577  				  "name": "Python Security ScanningAnalysis",
   578  				  "guid": null,
   579  				  "version": "1.2.0"
   580  				},
   581  				"deletable": true,
   582  				"warning": ""
   583  			  }
   584  			]`)
   585  	})
   586  
   587  	opts := &AnalysesListOptions{SarifID: String("8981cd8e-b078-4ac3-a3be-1dad7dbd0b582"), Ref: String("heads/master")}
   588  	ctx := context.Background()
   589  	analyses, _, err := client.CodeScanning.ListAnalysesForRepo(ctx, "o", "r", opts)
   590  	if err != nil {
   591  		t.Errorf("CodeScanning.ListAnalysesForRepo returned error: %v", err)
   592  	}
   593  
   594  	date := &Timestamp{time.Date(2020, time.August, 27, 15, 05, 21, 0, time.UTC)}
   595  	want := []*ScanningAnalysis{
   596  		{
   597  			ID:           Int64(201),
   598  			Ref:          String("refs/heads/main"),
   599  			CommitSHA:    String("d99612c3e1f2970085cfbaeadf8f010ef69bad83"),
   600  			AnalysisKey:  String(".github/workflows/codeql-analysis.yml:analyze"),
   601  			Environment:  String("{\"language\":\"python\"}"),
   602  			Error:        String(""),
   603  			Category:     String(".github/workflows/codeql-analysis.yml:analyze/language:python"),
   604  			CreatedAt:    date,
   605  			ResultsCount: Int(17),
   606  			RulesCount:   Int(49),
   607  			URL:          String("https://api.github.com/repos/o/r/code-scanning/analyses/201"),
   608  			SarifID:      String("8981cd8e-b078-4ac3-a3be-1dad7dbd0b582"),
   609  			Tool: &Tool{
   610  				Name:    String("CodeQL"),
   611  				GUID:    nil,
   612  				Version: String("2.4.0"),
   613  			},
   614  			Deletable: Bool(true),
   615  			Warning:   String(""),
   616  		},
   617  		{
   618  			ID:           Int64(200),
   619  			Ref:          String("refs/heads/my-branch"),
   620  			CommitSHA:    String("c8cff6510d4d084fb1b4aa13b64b97ca12b07321"),
   621  			AnalysisKey:  String(".github/workflows/shiftleft.yml:build"),
   622  			Environment:  String("{}"),
   623  			Error:        String(""),
   624  			Category:     String(".github/workflows/shiftleft.yml:build/"),
   625  			CreatedAt:    date,
   626  			ResultsCount: Int(17),
   627  			RulesCount:   Int(32),
   628  			URL:          String("https://api.github.com/repos/o/r/code-scanning/analyses/200"),
   629  			SarifID:      String("8981cd8e-b078-4ac3-a3be-1dad7dbd0b582"),
   630  			Tool: &Tool{
   631  				Name:    String("Python Security ScanningAnalysis"),
   632  				GUID:    nil,
   633  				Version: String("1.2.0"),
   634  			},
   635  			Deletable: Bool(true),
   636  			Warning:   String(""),
   637  		},
   638  	}
   639  	if !cmp.Equal(analyses, want) {
   640  		t.Errorf("CodeScanning.ListAnalysesForRepo returned %+v, want %+v", analyses, want)
   641  	}
   642  
   643  	const methodName = "ListAnalysesForRepo"
   644  	testBadOptions(t, methodName, func() (err error) {
   645  		_, _, err = client.CodeScanning.ListAnalysesForRepo(ctx, "\n", "\n", opts)
   646  		return err
   647  	})
   648  
   649  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   650  		got, resp, err := client.CodeScanning.ListAnalysesForRepo(ctx, "o", "r", opts)
   651  		if got != nil {
   652  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   653  		}
   654  		return resp, err
   655  	})
   656  }
   657  
   658  func TestCodeScanningService_GetAnalysis(t *testing.T) {
   659  	client, mux, _, teardown := setup()
   660  	defer teardown()
   661  
   662  	mux.HandleFunc("/repos/o/r/code-scanning/analyses/3602840", func(w http.ResponseWriter, r *http.Request) {
   663  		testMethod(t, r, "GET")
   664  		fmt.Fprint(w, `{
   665  			  "ref": "refs/heads/main",
   666  			  "commit_sha": "c18c69115654ff0166991962832dc2bd7756e655",
   667  			  "analysis_key": ".github/workflows/codeql-analysis.yml:analyze",
   668  			  "environment": "{\"language\":\"javascript\"}",
   669  			  "error": "",
   670  			  "category": ".github/workflows/codeql-analysis.yml:analyze/language:javascript",
   671  			  "created_at": "2021-01-13T11:55:49Z",
   672  			  "results_count": 3,
   673  			  "rules_count": 67,
   674  			  "id": 3602840,
   675  			  "url": "https://api.github.com/repos/o/r/code-scanning/analyses/201",
   676  			  "sarif_id": "47177e22-5596-11eb-80a1-c1e54ef945c6",
   677  			  "tool": {
   678  				"name": "CodeQL",
   679  				"guid": null,
   680  				"version": "2.4.0"
   681  			  },
   682  			  "deletable": true,
   683  			  "warning": ""
   684  			}`)
   685  	})
   686  
   687  	ctx := context.Background()
   688  	analysis, _, err := client.CodeScanning.GetAnalysis(ctx, "o", "r", 3602840)
   689  	if err != nil {
   690  		t.Errorf("CodeScanning.GetAnalysis returned error: %v", err)
   691  	}
   692  
   693  	date := &Timestamp{time.Date(2021, time.January, 13, 11, 55, 49, 0, time.UTC)}
   694  	want := &ScanningAnalysis{
   695  		ID:           Int64(3602840),
   696  		Ref:          String("refs/heads/main"),
   697  		CommitSHA:    String("c18c69115654ff0166991962832dc2bd7756e655"),
   698  		AnalysisKey:  String(".github/workflows/codeql-analysis.yml:analyze"),
   699  		Environment:  String("{\"language\":\"javascript\"}"),
   700  		Error:        String(""),
   701  		Category:     String(".github/workflows/codeql-analysis.yml:analyze/language:javascript"),
   702  		CreatedAt:    date,
   703  		ResultsCount: Int(3),
   704  		RulesCount:   Int(67),
   705  		URL:          String("https://api.github.com/repos/o/r/code-scanning/analyses/201"),
   706  		SarifID:      String("47177e22-5596-11eb-80a1-c1e54ef945c6"),
   707  		Tool: &Tool{
   708  			Name:    String("CodeQL"),
   709  			GUID:    nil,
   710  			Version: String("2.4.0"),
   711  		},
   712  		Deletable: Bool(true),
   713  		Warning:   String(""),
   714  	}
   715  	if !cmp.Equal(analysis, want) {
   716  		t.Errorf("CodeScanning.GetAnalysis returned %+v, want %+v", analysis, want)
   717  	}
   718  
   719  	const methodName = "GetAnalysis"
   720  	testBadOptions(t, methodName, func() (err error) {
   721  		_, _, err = client.CodeScanning.GetAnalysis(ctx, "\n", "\n", -123)
   722  		return err
   723  	})
   724  
   725  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   726  		got, resp, err := client.CodeScanning.GetAnalysis(ctx, "o", "r", 3602840)
   727  		if got != nil {
   728  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   729  		}
   730  		return resp, err
   731  	})
   732  }