github.com/google/go-github/v57@v57.0.0/github/security_advisories_test.go (about)

     1  // Copyright 2023 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  	"fmt"
    11  	"net/http"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  )
    18  
    19  func TestSecurityAdvisoriesService_RequestCVE(t *testing.T) {
    20  	client, mux, _, teardown := setup()
    21  	defer teardown()
    22  
    23  	mux.HandleFunc("/repos/o/r/security-advisories/ghsa_id_ok/cve", func(w http.ResponseWriter, r *http.Request) {
    24  		testMethod(t, r, "POST")
    25  		w.WriteHeader(http.StatusOK)
    26  	})
    27  
    28  	mux.HandleFunc("/repos/o/r/security-advisories/ghsa_id_accepted/cve", func(w http.ResponseWriter, r *http.Request) {
    29  		testMethod(t, r, "POST")
    30  		w.WriteHeader(http.StatusAccepted)
    31  	})
    32  
    33  	ctx := context.Background()
    34  	_, err := client.SecurityAdvisories.RequestCVE(ctx, "o", "r", "ghsa_id_ok")
    35  	if err != nil {
    36  		t.Errorf("SecurityAdvisoriesService.RequestCVE returned error: %v", err)
    37  	}
    38  
    39  	_, err = client.SecurityAdvisories.RequestCVE(ctx, "o", "r", "ghsa_id_accepted")
    40  	if err != nil {
    41  		t.Errorf("SecurityAdvisoriesService.RequestCVE returned error: %v", err)
    42  	}
    43  
    44  	const methodName = "RequestCVE"
    45  	testBadOptions(t, methodName, func() (err error) {
    46  		_, err = client.SecurityAdvisories.RequestCVE(ctx, "\n", "\n", "\n")
    47  		return err
    48  	})
    49  
    50  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
    51  		resp, err := client.SecurityAdvisories.RequestCVE(ctx, "o", "r", "ghsa_id")
    52  		if err == nil {
    53  			t.Errorf("testNewRequestAndDoFailure %v should have return err", methodName)
    54  		}
    55  		return resp, err
    56  	})
    57  }
    58  
    59  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisoriesForOrg_BadRequest(t *testing.T) {
    60  	client, mux, _, teardown := setup()
    61  	defer teardown()
    62  
    63  	mux.HandleFunc("/orgs/o/security-advisories", func(w http.ResponseWriter, r *http.Request) {
    64  		testMethod(t, r, "GET")
    65  
    66  		http.Error(w, "Bad Request", 400)
    67  	})
    68  
    69  	ctx := context.Background()
    70  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisoriesForOrg(ctx, "o", nil)
    71  	if err == nil {
    72  		t.Errorf("Expected HTTP 400 response")
    73  	}
    74  	if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want {
    75  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg return status %d, want %d", got, want)
    76  	}
    77  	if advisories != nil {
    78  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg return %+v, want nil", advisories)
    79  	}
    80  }
    81  
    82  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisoriesForOrg_NotFound(t *testing.T) {
    83  	client, mux, _, teardown := setup()
    84  	defer teardown()
    85  
    86  	mux.HandleFunc("/orgs/o/security-advisories", func(w http.ResponseWriter, r *http.Request) {
    87  		testMethod(t, r, "GET")
    88  
    89  		query := r.URL.Query()
    90  		if query.Get("state") != "draft" {
    91  			t.Errorf("ListRepositorySecurityAdvisoriesForOrg returned %+v, want %+v", query.Get("state"), "draft")
    92  		}
    93  
    94  		http.NotFound(w, r)
    95  	})
    96  
    97  	ctx := context.Background()
    98  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisoriesForOrg(ctx, "o", &ListRepositorySecurityAdvisoriesOptions{
    99  		State: "draft",
   100  	})
   101  	if err == nil {
   102  		t.Errorf("Expected HTTP 404 response")
   103  	}
   104  	if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want {
   105  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg return status %d, want %d", got, want)
   106  	}
   107  	if advisories != nil {
   108  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg return %+v, want nil", advisories)
   109  	}
   110  }
   111  
   112  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisoriesForOrg_UnmarshalError(t *testing.T) {
   113  	client, mux, _, teardown := setup()
   114  	defer teardown()
   115  
   116  	mux.HandleFunc("/orgs/o/security-advisories", func(w http.ResponseWriter, r *http.Request) {
   117  		testMethod(t, r, "GET")
   118  
   119  		w.WriteHeader(http.StatusOK)
   120  		assertWrite(t, w, []byte(`[{"ghsa_id": 12334354}]`))
   121  	})
   122  
   123  	ctx := context.Background()
   124  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisoriesForOrg(ctx, "o", nil)
   125  	if err == nil {
   126  		t.Errorf("Expected unmarshal error")
   127  	} else if !strings.Contains(err.Error(), "json: cannot unmarshal number into Go struct field SecurityAdvisory.ghsa_id of type string") {
   128  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg returned unexpected error: %v", err)
   129  	}
   130  	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
   131  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg return status %d, want %d", got, want)
   132  	}
   133  	if advisories != nil {
   134  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg return %+v, want nil", advisories)
   135  	}
   136  }
   137  
   138  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisoriesForOrg(t *testing.T) {
   139  	client, mux, _, teardown := setup()
   140  	defer teardown()
   141  
   142  	mux.HandleFunc("/orgs/o/security-advisories", func(w http.ResponseWriter, r *http.Request) {
   143  		testMethod(t, r, "GET")
   144  
   145  		w.WriteHeader(http.StatusOK)
   146  		assertWrite(t, w, []byte(`[
   147  			{
   148  				"ghsa_id": "GHSA-abcd-1234-efgh",
   149     				"cve_id": "CVE-2050-00000"
   150   			}
   151  		]`))
   152  	})
   153  
   154  	ctx := context.Background()
   155  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisoriesForOrg(ctx, "o", nil)
   156  	if err != nil {
   157  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg returned error: %v, want nil", err)
   158  	}
   159  	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
   160  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg return status %d, want %d", got, want)
   161  	}
   162  
   163  	want := []*SecurityAdvisory{
   164  		{
   165  			GHSAID: String("GHSA-abcd-1234-efgh"),
   166  			CVEID:  String("CVE-2050-00000"),
   167  		},
   168  	}
   169  	if !cmp.Equal(advisories, want) {
   170  		t.Errorf("ListRepositorySecurityAdvisoriesForOrg returned %+v, want %+v", advisories, want)
   171  	}
   172  
   173  	methodName := "ListRepositorySecurityAdvisoriesForOrg"
   174  	testBadOptions(t, methodName, func() (err error) {
   175  		_, _, err = client.SecurityAdvisories.ListRepositorySecurityAdvisoriesForOrg(ctx, "\n", &ListRepositorySecurityAdvisoriesOptions{
   176  			Sort: "\n",
   177  		})
   178  		return err
   179  	})
   180  
   181  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   182  		got, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisoriesForOrg(ctx, "o", nil)
   183  		if got != nil {
   184  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   185  		}
   186  		return resp, err
   187  	})
   188  }
   189  
   190  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisories_BadRequest(t *testing.T) {
   191  	client, mux, _, teardown := setup()
   192  	defer teardown()
   193  
   194  	mux.HandleFunc("/repos/o/r/security-advisories", func(w http.ResponseWriter, r *http.Request) {
   195  		testMethod(t, r, "GET")
   196  
   197  		http.Error(w, "Bad Request", 400)
   198  	})
   199  
   200  	ctx := context.Background()
   201  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisories(ctx, "o", "r", nil)
   202  	if err == nil {
   203  		t.Errorf("Expected HTTP 400 response")
   204  	}
   205  	if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want {
   206  		t.Errorf("ListRepositorySecurityAdvisories return status %d, want %d", got, want)
   207  	}
   208  	if advisories != nil {
   209  		t.Errorf("ListRepositorySecurityAdvisories return %+v, want nil", advisories)
   210  	}
   211  }
   212  
   213  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisories_NotFound(t *testing.T) {
   214  	client, mux, _, teardown := setup()
   215  	defer teardown()
   216  
   217  	mux.HandleFunc("/repos/o/r/security-advisories", func(w http.ResponseWriter, r *http.Request) {
   218  		testMethod(t, r, "GET")
   219  
   220  		query := r.URL.Query()
   221  		if query.Get("state") != "draft" {
   222  			t.Errorf("ListRepositorySecurityAdvisories returned %+v, want %+v", query.Get("state"), "draft")
   223  		}
   224  
   225  		http.NotFound(w, r)
   226  	})
   227  
   228  	ctx := context.Background()
   229  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisories(ctx, "o", "r", &ListRepositorySecurityAdvisoriesOptions{
   230  		State: "draft",
   231  	})
   232  	if err == nil {
   233  		t.Errorf("Expected HTTP 404 response")
   234  	}
   235  	if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want {
   236  		t.Errorf("ListRepositorySecurityAdvisories return status %d, want %d", got, want)
   237  	}
   238  	if advisories != nil {
   239  		t.Errorf("ListRepositorySecurityAdvisories return %+v, want nil", advisories)
   240  	}
   241  }
   242  
   243  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisories_UnmarshalError(t *testing.T) {
   244  	client, mux, _, teardown := setup()
   245  	defer teardown()
   246  
   247  	mux.HandleFunc("/repos/o/r/security-advisories", func(w http.ResponseWriter, r *http.Request) {
   248  		testMethod(t, r, "GET")
   249  
   250  		w.WriteHeader(http.StatusOK)
   251  		assertWrite(t, w, []byte(`[{"ghsa_id": 12334354}]`))
   252  	})
   253  
   254  	ctx := context.Background()
   255  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisories(ctx, "o", "r", nil)
   256  	if err == nil {
   257  		t.Errorf("Expected unmarshal error")
   258  	} else if !strings.Contains(err.Error(), "json: cannot unmarshal number into Go struct field SecurityAdvisory.ghsa_id of type string") {
   259  		t.Errorf("ListRepositorySecurityAdvisories returned unexpected error: %v", err)
   260  	}
   261  	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
   262  		t.Errorf("ListRepositorySecurityAdvisories return status %d, want %d", got, want)
   263  	}
   264  	if advisories != nil {
   265  		t.Errorf("ListRepositorySecurityAdvisories return %+v, want nil", advisories)
   266  	}
   267  }
   268  
   269  func TestSecurityAdvisoriesService_ListRepositorySecurityAdvisories(t *testing.T) {
   270  	client, mux, _, teardown := setup()
   271  	defer teardown()
   272  
   273  	mux.HandleFunc("/repos/o/r/security-advisories", func(w http.ResponseWriter, r *http.Request) {
   274  		testMethod(t, r, "GET")
   275  
   276  		w.WriteHeader(http.StatusOK)
   277  		assertWrite(t, w, []byte(`[
   278  			{
   279  				"ghsa_id": "GHSA-abcd-1234-efgh",
   280     				"cve_id": "CVE-2050-00000"
   281   			}
   282  		]`))
   283  	})
   284  
   285  	ctx := context.Background()
   286  	advisories, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisories(ctx, "o", "r", nil)
   287  	if err != nil {
   288  		t.Errorf("ListRepositorySecurityAdvisories returned error: %v, want nil", err)
   289  	}
   290  	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
   291  		t.Errorf("ListRepositorySecurityAdvisories return status %d, want %d", got, want)
   292  	}
   293  
   294  	want := []*SecurityAdvisory{
   295  		{
   296  			GHSAID: String("GHSA-abcd-1234-efgh"),
   297  			CVEID:  String("CVE-2050-00000"),
   298  		},
   299  	}
   300  	if !cmp.Equal(advisories, want) {
   301  		t.Errorf("ListRepositorySecurityAdvisories returned %+v, want %+v", advisories, want)
   302  	}
   303  
   304  	methodName := "ListRepositorySecurityAdvisories"
   305  	testBadOptions(t, methodName, func() (err error) {
   306  		_, _, err = client.SecurityAdvisories.ListRepositorySecurityAdvisories(ctx, "\n", "\n", &ListRepositorySecurityAdvisoriesOptions{
   307  			Sort: "\n",
   308  		})
   309  		return err
   310  	})
   311  
   312  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   313  		got, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisories(ctx, "o", "r", nil)
   314  		if got != nil {
   315  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   316  		}
   317  		return resp, err
   318  	})
   319  }
   320  
   321  func TestListGlobalSecurityAdvisories(t *testing.T) {
   322  	client, mux, _, teardown := setup()
   323  	defer teardown()
   324  
   325  	mux.HandleFunc("/advisories", func(w http.ResponseWriter, r *http.Request) {
   326  		testMethod(t, r, "GET")
   327  		testFormValues(t, r, values{"cve_id": "CVE-xoxo-1234"})
   328  
   329  		fmt.Fprint(w, `[{
   330  				"id": 1,
   331  				"ghsa_id": "GHSA-xoxo-1234-xoxo",
   332  				"cve_id": "CVE-xoxo-1234",
   333  				"url": "https://api.github.com/advisories/GHSA-xoxo-1234-xoxo",
   334  				"html_url": "https://github.com/advisories/GHSA-xoxo-1234-xoxo",
   335  				"repository_advisory_url": "https://api.github.com/repos/project/a-package/security-advisories/GHSA-xoxo-1234-xoxo",
   336  				"summary": "Heartbleed security advisory",
   337  				"description": "This bug allows an attacker to read portions of the affected server’s memory, potentially disclosing sensitive information.",
   338  				"type": "reviewed",
   339  				"severity": "high",
   340  				"source_code_location": "https://github.com/project/a-package",
   341  				"identifiers": [
   342  					{
   343  						"type": "GHSA",
   344  						"value": "GHSA-xoxo-1234-xoxo"
   345  					},
   346  					{
   347  						"type": "CVE",
   348  						"value": "CVE-xoxo-1234"
   349  					}
   350  				],
   351  				"references": ["https://nvd.nist.gov/vuln/detail/CVE-xoxo-1234"],
   352  				"published_at": "1996-06-20T00:00:00Z",
   353  				"updated_at": "1996-06-20T00:00:00Z",
   354  				"github_reviewed_at": "1996-06-20T00:00:00Z",
   355  				"nvd_published_at": "1996-06-20T00:00:00Z",
   356  				"withdrawn_at": null,
   357  				"vulnerabilities": [
   358  					{
   359  						"package": {
   360  							"ecosystem": "npm",
   361  							"name": "a-package"
   362  						},
   363  						"first_patched_version": "1.0.3",
   364  						"vulnerable_version_range": "<=1.0.2",
   365  						"vulnerable_functions": ["a_function"]
   366  					}
   367  				],
   368  				"cvss": {
   369  					"vector_string": "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H",
   370  					"score": 7.6
   371  				},
   372  				"cwes": [
   373  					{
   374  						"cwe_id": "CWE-400",
   375  						"name": "Uncontrolled Resource Consumption"
   376  					}
   377  				],
   378  				"credits": [
   379  					{
   380  						"user": {
   381  							"login": "user",
   382  							"id": 1,
   383  							"node_id": "12=",
   384  							"avatar_url": "a",
   385  							"gravatar_id": "",
   386  							"url": "a",
   387  							"html_url": "b",
   388  							"followers_url": "b",
   389  							"following_url": "c",
   390  							"gists_url": "d",
   391  							"starred_url": "e",
   392  							"subscriptions_url": "f",
   393  							"organizations_url": "g",
   394  							"repos_url": "h",
   395  							"events_url": "i",
   396  							"received_events_url": "j",
   397  							"type": "User",
   398  							"site_admin": false
   399  						},
   400  						"type": "analyst"
   401  					}
   402  				]
   403  			}
   404  		]`)
   405  	})
   406  
   407  	ctx := context.Background()
   408  	opts := &ListGlobalSecurityAdvisoriesOptions{CVEID: String("CVE-xoxo-1234")}
   409  
   410  	advisories, _, err := client.SecurityAdvisories.ListGlobalSecurityAdvisories(ctx, opts)
   411  	if err != nil {
   412  		t.Errorf("SecurityAdvisories.ListGlobalSecurityAdvisories returned error: %v", err)
   413  	}
   414  
   415  	date := Timestamp{time.Date(1996, time.June, 20, 00, 00, 00, 0, time.UTC)}
   416  	want := []*GlobalSecurityAdvisory{
   417  		{
   418  			ID: Int64(1),
   419  			SecurityAdvisory: SecurityAdvisory{
   420  				GHSAID:      String("GHSA-xoxo-1234-xoxo"),
   421  				CVEID:       String("CVE-xoxo-1234"),
   422  				URL:         String("https://api.github.com/advisories/GHSA-xoxo-1234-xoxo"),
   423  				HTMLURL:     String("https://github.com/advisories/GHSA-xoxo-1234-xoxo"),
   424  				Severity:    String("high"),
   425  				Summary:     String("Heartbleed security advisory"),
   426  				Description: String("This bug allows an attacker to read portions of the affected server’s memory, potentially disclosing sensitive information."),
   427  				Identifiers: []*AdvisoryIdentifier{
   428  					{
   429  						Type:  String("GHSA"),
   430  						Value: String("GHSA-xoxo-1234-xoxo"),
   431  					},
   432  					{
   433  						Type:  String("CVE"),
   434  						Value: String("CVE-xoxo-1234"),
   435  					},
   436  				},
   437  				PublishedAt: &date,
   438  				UpdatedAt:   &date,
   439  				WithdrawnAt: nil,
   440  				CVSS: &AdvisoryCVSS{
   441  					VectorString: String("CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H"),
   442  					Score:        Float64(7.6),
   443  				},
   444  				CWEs: []*AdvisoryCWEs{
   445  					{
   446  						CWEID: String("CWE-400"),
   447  						Name:  String("Uncontrolled Resource Consumption"),
   448  					},
   449  				},
   450  			},
   451  			References: []string{"https://nvd.nist.gov/vuln/detail/CVE-xoxo-1234"},
   452  			Vulnerabilities: []*GlobalSecurityVulnerability{
   453  				{
   454  					Package: &VulnerabilityPackage{
   455  						Ecosystem: String("npm"),
   456  						Name:      String("a-package"),
   457  					},
   458  					FirstPatchedVersion:    String("1.0.3"),
   459  					VulnerableVersionRange: String("<=1.0.2"),
   460  					VulnerableFunctions:    []string{"a_function"},
   461  				},
   462  			},
   463  			RepositoryAdvisoryURL: String("https://api.github.com/repos/project/a-package/security-advisories/GHSA-xoxo-1234-xoxo"),
   464  			Type:                  String("reviewed"),
   465  			SourceCodeLocation:    String("https://github.com/project/a-package"),
   466  			GithubReviewedAt:      &date,
   467  			NVDPublishedAt:        &date,
   468  			Credits: []*Credit{
   469  				{
   470  					User: &User{
   471  						Login:             String("user"),
   472  						ID:                Int64(1),
   473  						NodeID:            String("12="),
   474  						AvatarURL:         String("a"),
   475  						GravatarID:        String(""),
   476  						URL:               String("a"),
   477  						HTMLURL:           String("b"),
   478  						FollowersURL:      String("b"),
   479  						FollowingURL:      String("c"),
   480  						GistsURL:          String("d"),
   481  						StarredURL:        String("e"),
   482  						SubscriptionsURL:  String("f"),
   483  						OrganizationsURL:  String("g"),
   484  						ReposURL:          String("h"),
   485  						EventsURL:         String("i"),
   486  						ReceivedEventsURL: String("j"),
   487  						Type:              String("User"),
   488  						SiteAdmin:         Bool(false),
   489  					},
   490  					Type: String("analyst"),
   491  				},
   492  			},
   493  		},
   494  	}
   495  
   496  	if !cmp.Equal(advisories, want) {
   497  		t.Errorf("SecurityAdvisories.ListGlobalSecurityAdvisories %+v, want %+v", advisories, want)
   498  	}
   499  
   500  	const methodName = "ListGlobalSecurityAdvisories"
   501  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   502  		_, resp, err := client.SecurityAdvisories.ListGlobalSecurityAdvisories(ctx, nil)
   503  		return resp, err
   504  	})
   505  }
   506  
   507  func TestGetGlobalSecurityAdvisories(t *testing.T) {
   508  	client, mux, _, teardown := setup()
   509  	defer teardown()
   510  
   511  	mux.HandleFunc("/advisories/GHSA-xoxo-1234-xoxo", func(w http.ResponseWriter, r *http.Request) {
   512  		testMethod(t, r, "GET")
   513  
   514  		fmt.Fprint(w, `{
   515  			"id": 1,
   516  			"ghsa_id": "GHSA-xoxo-1234-xoxo",
   517  			"cve_id": "CVE-xoxo-1234",
   518  			"url": "https://api.github.com/advisories/GHSA-xoxo-1234-xoxo",
   519  			"html_url": "https://github.com/advisories/GHSA-xoxo-1234-xoxo",
   520  			"repository_advisory_url": "https://api.github.com/repos/project/a-package/security-advisories/GHSA-xoxo-1234-xoxo",
   521  			"summary": "Heartbleed security advisory",
   522  			"description": "This bug allows an attacker to read portions of the affected server’s memory, potentially disclosing sensitive information.",
   523  			"type": "reviewed",
   524  			"severity": "high",
   525  			"source_code_location": "https://github.com/project/a-package",
   526  			"identifiers": [
   527  				{
   528  					"type": "GHSA",
   529  					"value": "GHSA-xoxo-1234-xoxo"
   530  				},
   531  				{
   532  					"type": "CVE",
   533  					"value": "CVE-xoxo-1234"
   534  				}
   535  			],
   536  			"references": ["https://nvd.nist.gov/vuln/detail/CVE-xoxo-1234"],
   537  			"published_at": "1996-06-20T00:00:00Z",
   538  			"updated_at": "1996-06-20T00:00:00Z",
   539  			"github_reviewed_at": "1996-06-20T00:00:00Z",
   540  			"nvd_published_at": "1996-06-20T00:00:00Z",
   541  			"withdrawn_at": null,
   542  			"vulnerabilities": [
   543  				{
   544  					"package": {
   545  						"ecosystem": "npm",
   546  						"name": "a-package"
   547  					},
   548  					"first_patched_version": "1.0.3",
   549  					"vulnerable_version_range": "<=1.0.2",
   550  					"vulnerable_functions": ["a_function"]
   551  				}
   552  			],
   553  			"cvss": {
   554  				"vector_string": "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H",
   555  				"score": 7.6
   556  			},
   557  			"cwes": [
   558  				{
   559  					"cwe_id": "CWE-400",
   560  					"name": "Uncontrolled Resource Consumption"
   561  				}
   562  			],
   563  			"credits": [
   564  				{
   565  					"user": {
   566  						"login": "user",
   567  						"id": 1,
   568  						"node_id": "12=",
   569  						"avatar_url": "a",
   570  						"gravatar_id": "",
   571  						"url": "a",
   572  						"html_url": "b",
   573  						"followers_url": "b",
   574  						"following_url": "c",
   575  						"gists_url": "d",
   576  						"starred_url": "e",
   577  						"subscriptions_url": "f",
   578  						"organizations_url": "g",
   579  						"repos_url": "h",
   580  						"events_url": "i",
   581  						"received_events_url": "j",
   582  						"type": "User",
   583  						"site_admin": false
   584  					},
   585  					"type": "analyst"
   586  				}
   587  			]
   588  		}`)
   589  	})
   590  
   591  	ctx := context.Background()
   592  	advisory, _, err := client.SecurityAdvisories.GetGlobalSecurityAdvisories(ctx, "GHSA-xoxo-1234-xoxo")
   593  	if err != nil {
   594  		t.Errorf("SecurityAdvisories.GetGlobalSecurityAdvisories returned error: %v", err)
   595  	}
   596  
   597  	date := Timestamp{time.Date(1996, time.June, 20, 00, 00, 00, 0, time.UTC)}
   598  	want := &GlobalSecurityAdvisory{
   599  		ID: Int64(1),
   600  		SecurityAdvisory: SecurityAdvisory{
   601  			GHSAID:      String("GHSA-xoxo-1234-xoxo"),
   602  			CVEID:       String("CVE-xoxo-1234"),
   603  			URL:         String("https://api.github.com/advisories/GHSA-xoxo-1234-xoxo"),
   604  			HTMLURL:     String("https://github.com/advisories/GHSA-xoxo-1234-xoxo"),
   605  			Severity:    String("high"),
   606  			Summary:     String("Heartbleed security advisory"),
   607  			Description: String("This bug allows an attacker to read portions of the affected server’s memory, potentially disclosing sensitive information."),
   608  			Identifiers: []*AdvisoryIdentifier{
   609  				{
   610  					Type:  String("GHSA"),
   611  					Value: String("GHSA-xoxo-1234-xoxo"),
   612  				},
   613  				{
   614  					Type:  String("CVE"),
   615  					Value: String("CVE-xoxo-1234"),
   616  				},
   617  			},
   618  			PublishedAt: &date,
   619  			UpdatedAt:   &date,
   620  			WithdrawnAt: nil,
   621  			CVSS: &AdvisoryCVSS{
   622  				VectorString: String("CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H"),
   623  				Score:        Float64(7.6),
   624  			},
   625  			CWEs: []*AdvisoryCWEs{
   626  				{
   627  					CWEID: String("CWE-400"),
   628  					Name:  String("Uncontrolled Resource Consumption"),
   629  				},
   630  			},
   631  		},
   632  		RepositoryAdvisoryURL: String("https://api.github.com/repos/project/a-package/security-advisories/GHSA-xoxo-1234-xoxo"),
   633  		Type:                  String("reviewed"),
   634  		SourceCodeLocation:    String("https://github.com/project/a-package"),
   635  		References:            []string{"https://nvd.nist.gov/vuln/detail/CVE-xoxo-1234"},
   636  		GithubReviewedAt:      &date,
   637  		NVDPublishedAt:        &date,
   638  
   639  		Vulnerabilities: []*GlobalSecurityVulnerability{
   640  			{
   641  				Package: &VulnerabilityPackage{
   642  					Ecosystem: String("npm"),
   643  					Name:      String("a-package"),
   644  				},
   645  				FirstPatchedVersion:    String("1.0.3"),
   646  				VulnerableVersionRange: String("<=1.0.2"),
   647  				VulnerableFunctions:    []string{"a_function"},
   648  			},
   649  		},
   650  		Credits: []*Credit{
   651  			{
   652  				User: &User{
   653  					Login:             String("user"),
   654  					ID:                Int64(1),
   655  					NodeID:            String("12="),
   656  					AvatarURL:         String("a"),
   657  					GravatarID:        String(""),
   658  					URL:               String("a"),
   659  					HTMLURL:           String("b"),
   660  					FollowersURL:      String("b"),
   661  					FollowingURL:      String("c"),
   662  					GistsURL:          String("d"),
   663  					StarredURL:        String("e"),
   664  					SubscriptionsURL:  String("f"),
   665  					OrganizationsURL:  String("g"),
   666  					ReposURL:          String("h"),
   667  					EventsURL:         String("i"),
   668  					ReceivedEventsURL: String("j"),
   669  					Type:              String("User"),
   670  					SiteAdmin:         Bool(false),
   671  				},
   672  				Type: String("analyst"),
   673  			},
   674  		},
   675  	}
   676  
   677  	if !cmp.Equal(advisory, want) {
   678  		t.Errorf("SecurityAdvisories.GetGlobalSecurityAdvisories %+v, want %+v", advisory, want)
   679  	}
   680  
   681  	const methodName = "GetGlobalSecurityAdvisories"
   682  
   683  	testBadOptions(t, methodName, func() (err error) {
   684  		_, _, err = client.SecurityAdvisories.GetGlobalSecurityAdvisories(ctx, "CVE-\n-1234")
   685  		return err
   686  	})
   687  
   688  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   689  		got, resp, err := client.SecurityAdvisories.GetGlobalSecurityAdvisories(ctx, "e")
   690  		if got != nil {
   691  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   692  		}
   693  		return resp, err
   694  	})
   695  }