diff --git a/github/enterprise_audit_log_test.go b/github/enterprise_audit_log_test.go index bc2369090fb..bf6e04dce81 100644 --- a/github/enterprise_audit_log_test.go +++ b/github/enterprise_audit_log_test.go @@ -7,6 +7,7 @@ package github import ( "context" + "encoding/json" "fmt" "net/http" "testing" @@ -60,7 +61,7 @@ func TestEnterpriseService_GetAuditLog(t *testing.T) { Action: Ptr("workflows.completed_workflow_run"), Actor: Ptr("testactor"), CreatedAt: &Timestamp{timestamp}, - Org: Ptr("o"), + Org: json.RawMessage(`"o"`), AdditionalFields: map[string]interface{}{ "completed_at": "2021-03-07T00:35:08.000Z", "conclusion": "success", diff --git a/github/gen-accessors.go b/github/gen-accessors.go index 261b945e426..1bab7e45155 100644 --- a/github/gen-accessors.go +++ b/github/gen-accessors.go @@ -45,6 +45,8 @@ var ( "ErrorResponse.GetResponse": true, "RateLimitError.GetResponse": true, "AbuseRateLimitError.GetResponse": true, + "AuditEntry.GetOrgID": true, + "AuditEntry.GetOrg": true, } // skipStructs lists structs to skip. skipStructs = map[string]bool{ diff --git a/github/github-accessors.go b/github/github-accessors.go index e2a165272ef..7f8ba092fb6 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -1166,22 +1166,6 @@ func (a *AuditEntry) GetHashedToken() string { return *a.HashedToken } -// GetOrg returns the Org field if it's non-nil, zero value otherwise. -func (a *AuditEntry) GetOrg() string { - if a == nil || a.Org == nil { - return "" - } - return *a.Org -} - -// GetOrgID returns the OrgID field if it's non-nil, zero value otherwise. -func (a *AuditEntry) GetOrgID() int64 { - if a == nil || a.OrgID == nil { - return 0 - } - return *a.OrgID -} - // GetTimestamp returns the Timestamp field if it's non-nil, zero value otherwise. func (a *AuditEntry) GetTimestamp() Timestamp { if a == nil || a.Timestamp == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 66132f57664..0f7c0215a69 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -1518,28 +1518,6 @@ func TestAuditEntry_GetHashedToken(tt *testing.T) { a.GetHashedToken() } -func TestAuditEntry_GetOrg(tt *testing.T) { - tt.Parallel() - var zeroValue string - a := &AuditEntry{Org: &zeroValue} - a.GetOrg() - a = &AuditEntry{} - a.GetOrg() - a = nil - a.GetOrg() -} - -func TestAuditEntry_GetOrgID(tt *testing.T) { - tt.Parallel() - var zeroValue int64 - a := &AuditEntry{OrgID: &zeroValue} - a.GetOrgID() - a = &AuditEntry{} - a.GetOrgID() - a = nil - a.GetOrgID() -} - func TestAuditEntry_GetTimestamp(tt *testing.T) { tt.Parallel() var zeroValue Timestamp diff --git a/github/orgs_audit_log.go b/github/orgs_audit_log.go index 025c5d02327..93e2ab140d2 100644 --- a/github/orgs_audit_log.go +++ b/github/orgs_audit_log.go @@ -30,24 +30,24 @@ type ActorLocation struct { // in AdditionalFields. // For a list of actions see - https://docs.github.com/github/setting-up-and-managing-organizations-and-teams/reviewing-the-audit-log-for-your-organization#audit-log-actions type AuditEntry struct { - Action *string `json:"action,omitempty"` // The name of the action that was performed, for example `user.login` or `repo.create`. - Actor *string `json:"actor,omitempty"` // The actor who performed the action. - ActorID *int64 `json:"actor_id,omitempty"` - ActorLocation *ActorLocation `json:"actor_location,omitempty"` - Business *string `json:"business,omitempty"` - BusinessID *int64 `json:"business_id,omitempty"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - DocumentID *string `json:"_document_id,omitempty"` - ExternalIdentityNameID *string `json:"external_identity_nameid,omitempty"` - ExternalIdentityUsername *string `json:"external_identity_username,omitempty"` - HashedToken *string `json:"hashed_token,omitempty"` - Org *string `json:"org,omitempty"` - OrgID *int64 `json:"org_id,omitempty"` - Timestamp *Timestamp `json:"@timestamp,omitempty"` // The time the audit log event occurred, given as a [Unix timestamp](http://en.wikipedia.org/wiki/Unix_time). - TokenID *int64 `json:"token_id,omitempty"` - TokenScopes *string `json:"token_scopes,omitempty"` - User *string `json:"user,omitempty"` // The user that was affected by the action performed (if available). - UserID *int64 `json:"user_id,omitempty"` + Action *string `json:"action,omitempty"` // The name of the action that was performed, for example `user.login` or `repo.create`. + Actor *string `json:"actor,omitempty"` // The actor who performed the action. + ActorID *int64 `json:"actor_id,omitempty"` + ActorLocation *ActorLocation `json:"actor_location,omitempty"` + Business *string `json:"business,omitempty"` + BusinessID *int64 `json:"business_id,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + DocumentID *string `json:"_document_id,omitempty"` + ExternalIdentityNameID *string `json:"external_identity_nameid,omitempty"` + ExternalIdentityUsername *string `json:"external_identity_username,omitempty"` + HashedToken *string `json:"hashed_token,omitempty"` + Org json.RawMessage `json:"org,omitempty"` + OrgID json.RawMessage `json:"org_id,omitempty"` + Timestamp *Timestamp `json:"@timestamp,omitempty"` // The time the audit log event occurred, given as a [Unix timestamp](http://en.wikipedia.org/wiki/Unix_time). + TokenID *int64 `json:"token_id,omitempty"` + TokenScopes *string `json:"token_scopes,omitempty"` + User *string `json:"user,omitempty"` // The user that was affected by the action performed (if available). + UserID *int64 `json:"user_id,omitempty"` // Some events types have a data field that contains additional information about the event. Data map[string]interface{} `json:"data,omitempty"` @@ -56,6 +56,72 @@ type AuditEntry struct { AdditionalFields map[string]interface{} `json:"-"` } +// GetOrg returns the Org field, as a string, if it's valid. +func (a *AuditEntry) GetOrg() (org string, ok bool) { + if a == nil || a.Org == nil { + return "", false + } + if err := json.Unmarshal([]byte(a.Org), &org); err != nil { + return "", false + } + + return org, true +} + +// GetOrgNames returns the Org field, as a slice of strings, if it's valid. +func (a *AuditEntry) GetOrgNames() (names []string, ok bool) { + if a == nil || a.Org == nil { + return nil, false + } + if err := json.Unmarshal([]byte(a.Org), &names); err != nil { + return nil, false + } + + return names, true +} + +// GetRawOrg returns the Org field as a json.RawMessage. +func (a *AuditEntry) GetRawOrg() json.RawMessage { + if a == nil || a.Org == nil { + return json.RawMessage{} + } + + return a.Org +} + +// GetOrgID returns the OrgID field, as an int64, if it's valid. +func (a *AuditEntry) GetOrgID() (orgID int64, ok bool) { + if a == nil || a.OrgID == nil { + return 0, false + } + if err := json.Unmarshal([]byte(a.OrgID), &orgID); err != nil { + return 0, false + } + + return orgID, true +} + +// GetOrgIDs returns the OrgID field, as a slice of int64, if it's valid. +func (a *AuditEntry) GetOrgIDs() (orgIDs []int64, ok bool) { + if a == nil || a.OrgID == nil { + return nil, false + } + if err := json.Unmarshal([]byte(a.OrgID), &orgIDs); err != nil { + return nil, false + } + + return orgIDs, true +} + +// GetRawOrgID returns the OrgID field as a json.RawMessage. +func (a *AuditEntry) GetRawOrgID() json.RawMessage { + if a == nil || a.OrgID == nil { + return json.RawMessage{} + } + + return a.OrgID +} + func (a *AuditEntry) UnmarshalJSON(data []byte) error { type entryAlias AuditEntry var v entryAlias diff --git a/github/orgs_audit_log_test.go b/github/orgs_audit_log_test.go index f4b617c587b..08e7b99d6b0 100644 --- a/github/orgs_audit_log_test.go +++ b/github/orgs_audit_log_test.go @@ -7,6 +7,7 @@ package github import ( "context" + "encoding/json" "fmt" "net/http" "strings" @@ -93,6 +94,8 @@ func TestOrganizationService_GetAuditLog(t *testing.T) { } timestamp := time.Unix(0, 1615077308538*1e6) + orgID, _ := json.Marshal(Ptr(int64(1))) + want := []*AuditEntry{ { Timestamp: &Timestamp{timestamp}, @@ -104,8 +107,8 @@ func TestOrganizationService_GetAuditLog(t *testing.T) { }, CreatedAt: &Timestamp{timestamp}, HashedToken: Ptr("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), - Org: Ptr("o"), - OrgID: Ptr(int64(1)), + Org: json.RawMessage(`"o"`), + OrgID: orgID, TokenID: Ptr(int64(1)), TokenScopes: Ptr("gist,repo:read"), AdditionalFields: map[string]interface{}{ @@ -225,6 +228,8 @@ func TestAuditEntry_Marshal(t *testing.T) { t.Parallel() testJSONMarshal(t, &AuditEntry{}, "{}") + orgID, _ := json.Marshal(Ptr(int64(1))) + u := &AuditEntry{ Action: Ptr("a"), Actor: Ptr("ac"), @@ -235,8 +240,8 @@ func TestAuditEntry_Marshal(t *testing.T) { ExternalIdentityNameID: Ptr("ein"), ExternalIdentityUsername: Ptr("eiu"), HashedToken: Ptr("ht"), - Org: Ptr("o"), - OrgID: Ptr(int64(1)), + Org: json.RawMessage(`"o"`), + OrgID: orgID, Timestamp: &Timestamp{referenceTime}, TokenID: Ptr(int64(1)), TokenScopes: Ptr("ts"), @@ -421,3 +426,177 @@ func TestAuditEntry_Marshal(t *testing.T) { testJSONMarshal(t, u, want) } +func TestAuditEntry_Getters(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + entry *AuditEntry + wantOrg string + wantOrgOk bool + wantRawOrg json.RawMessage + wantOrgID int64 + wantOrgIDOk bool + wantRawOrgID json.RawMessage + wantOrgSlice []string + wantOrgSliceOk bool + wantOrgIDSlice []int64 + wantOrgIDSliceOk bool + }{ + { + name: "nil entry", + entry: nil, + wantOrg: "", + wantOrgOk: false, + wantRawOrg: json.RawMessage{}, + wantOrgID: 0, + wantOrgIDOk: false, + wantRawOrgID: json.RawMessage{}, + wantOrgSlice: []string{}, + wantOrgSliceOk: false, + wantOrgIDSlice: []int64{}, + wantOrgIDSliceOk: false, + }, + { + name: "nil Org field", + entry: &AuditEntry{}, + wantOrg: "", + wantOrgOk: false, + wantRawOrg: json.RawMessage{}, + wantOrgID: 0, + wantOrgIDOk: false, + wantRawOrgID: json.RawMessage{}, + wantOrgSlice: []string{}, + wantOrgSliceOk: false, + wantOrgIDSlice: []int64{}, + wantOrgIDSliceOk: false, + }, + { + name: "valid Org field", + entry: &AuditEntry{ + Org: json.RawMessage(`"testorg"`), + OrgID: json.RawMessage(`1`), + }, + wantOrg: "testorg", + wantOrgOk: true, + wantRawOrg: json.RawMessage(`"testorg"`), + wantOrgID: 1, + wantOrgIDOk: true, + wantRawOrgID: json.RawMessage(`1`), + wantOrgSlice: []string{}, + wantOrgSliceOk: false, + wantOrgIDSlice: []int64{}, + wantOrgIDSliceOk: false, + }, + { + name: "invalid Org field", + entry: &AuditEntry{ + Org: json.RawMessage(`{"invalid": "json"}`), + OrgID: json.RawMessage(`"invalid"`), + }, + wantOrg: "", + wantOrgOk: false, + wantRawOrg: json.RawMessage(`{"invalid": "json"}`), + wantOrgID: 0, + wantOrgIDOk: false, + wantRawOrgID: json.RawMessage(`"invalid"`), + wantOrgSlice: []string{}, + wantOrgSliceOk: false, + wantOrgIDSlice: []int64{}, + wantOrgIDSliceOk: false, + }, + { + name: "valid Org field", + entry: &AuditEntry{ + Org: json.RawMessage(`["testOrg1", "testOrg2", "testOrg3"]`), + }, + wantOrg: "", + wantOrgOk: false, + wantRawOrg: json.RawMessage(`["testOrg1", "testOrg2", "testOrg3"]`), + wantOrgID: 0, + wantOrgIDOk: false, + wantRawOrgID: json.RawMessage{}, + wantOrgSlice: []string{"testOrg1", "testOrg2", "testOrg3"}, + wantOrgSliceOk: true, + wantOrgIDSlice: []int64{}, + wantOrgIDSliceOk: false, + }, + { + name: "valid OrgID field", + entry: &AuditEntry{ + OrgID: json.RawMessage(`[1, 2, 3]`), + }, + wantOrg: "", + wantOrgOk: false, + wantRawOrg: json.RawMessage{}, + wantOrgID: 0, + wantOrgIDOk: false, + wantRawOrgID: json.RawMessage(`[1, 2, 3]`), + wantOrgSlice: []string{}, + wantOrgSliceOk: false, + wantOrgIDSlice: []int64{1, 2, 3}, + wantOrgIDSliceOk: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + gotOrg, gotOrgOk := tt.entry.GetOrg() + if gotOrg != tt.wantOrg || gotOrgOk != tt.wantOrgOk { + t.Errorf("GetOrg() = %v, %v; want %v, %v", gotOrg, gotOrgOk, tt.wantOrg, tt.wantOrgOk) + } + + gotRawOrg := tt.entry.GetRawOrg() + if string(gotRawOrg) != string(tt.wantRawOrg) { + t.Errorf("GetRawOrg() = %v; want %v", string(gotRawOrg), string(tt.wantRawOrg)) + } + + gotOrgID, gotOrgIDOk := tt.entry.GetOrgID() + if gotOrgID != tt.wantOrgID || gotOrgIDOk != tt.wantOrgIDOk { + t.Errorf("GetOrgID() = %v, %v; want %v, %v", gotOrgID, gotOrgIDOk, tt.wantOrgID, tt.wantOrgIDOk) + } + + gotRawOrgID := tt.entry.GetRawOrgID() + if string(gotRawOrgID) != string(tt.wantRawOrgID) { + t.Errorf("GetRawOrgID() = %v; want %v", string(gotRawOrgID), string(tt.wantRawOrgID)) + } + + gotOrgSlice, gotOrgSliceOk := tt.entry.GetOrgNames() + if !equalStringSlices(gotOrgSlice, tt.wantOrgSlice) || gotOrgSliceOk != tt.wantOrgSliceOk { + t.Errorf("GetOrgSlice() = %v, %v; want %v, %v", gotOrgSlice, gotOrgSliceOk, tt.wantOrgSlice, tt.wantOrgSliceOk) + } + + gotOrgIDSlice, gotOrgIDSliceOk := tt.entry.GetOrgIDs() + if !equalInt64Slices(gotOrgIDSlice, tt.wantOrgIDSlice) || gotOrgIDSliceOk != tt.wantOrgIDSliceOk { + t.Errorf("GetOrgIDSlice() = %v, %v; want %v, %v", gotOrgIDSlice, gotOrgIDSliceOk, tt.wantOrgIDSlice, tt.wantOrgIDSliceOk) + } + }) + } +} + +// equalStringSlices is a testing helper function and returns true if the two slices are equal. +func equalStringSlices(actual, expected []string) bool { + if len(actual) != len(expected) { + return false + } + for i := range actual { + if actual[i] != expected[i] { + return false + } + } + return true +} + +// equalInt64Slices is a testing helper function and returns true if the two slices are equal. +func equalInt64Slices(actual, expected []int64) bool { + if len(actual) != len(expected) { + return false + } + for i := range actual { + if actual[i] != expected[i] { + return false + } + } + return true +}