//
// Copyright 2021, Sander van Harmelen
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

const (
	ExpectedGroup     = "webhook-test"
	expectedAvatar    = "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
	expectedEmail     = "user1@example.com"
	expectedEventName = "user_add_to_group"
	expectedID        = 1
	expectedName      = "User1"
	expectedUsername  = "user1"
)

func TestBuildEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/build.json")

	var event *BuildEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Build Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Build Event is null")
	}

	if event.BuildID != 1977 {
		t.Errorf("BuildID is %v, want %v", event.BuildID, 1977)
	}

	if event.User.ID != 42 {
		t.Errorf("User ID is %d, want %d", event.User.ID, 42)
	}

	if event.User.Name != expectedName {
		t.Errorf("Username is %s, want %s", event.User.Name, expectedName)
	}

	if event.BuildCreatedAt != "2021-02-23T02:41:37.886Z" {
		t.Errorf("BuildCreatedAt is %s, want 2021-02-23T02:41:37.886Z", event.BuildCreatedAt)
	}
}

func TestCommitCommentEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/note_commit.json")

	var event *CommitCommentEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Commit Comment Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Commit Comment Event is null")
	}

	if event.ObjectKind != string(NoteEventTargetType) {
		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
	}

	if event.EventType != "note" {
		t.Errorf("EventType is %v, want %v", event.EventType, "note")
	}

	if event.ProjectID != 5 {
		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
	}

	if event.User.ID != 42 {
		t.Errorf("User ID is %d, want %d", event.User.ID, 42)
	}

	if event.Repository.Name != "GitLab Test" {
		t.Errorf("Repository name is %v, want %v", event.Repository.Name, "GitLab Test")
	}

	if event.ObjectAttributes.NoteableType != "Commit" {
		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Commit")
	}

	if event.ObjectAttributes.Action != CommentEventActionCreate {
		t.Errorf("Action is %v, want %v", event.ObjectAttributes.Action, "create")
	}

	if event.Commit.Title != "Add submodule" {
		t.Errorf("Issue title is %v, want %v", event.Commit.Title, "Add submodule")
	}
}

func TestJobEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/job.json")

	var event *JobEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Job Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Job Event is null")
	}

	expectedEvent := JobEvent{
		ObjectKind:          "build",
		Ref:                 "main",
		Tag:                 false,
		BeforeSHA:           "0000000000000000000000000000000000000000",
		SHA:                 "95d49d1efbd941908580e79d65e4b5ecaf4a8305",
		BuildID:             3580121225,
		BuildName:           "auto_deploy:start",
		BuildStage:          "coordinated:tag",
		BuildStatus:         "success",
		BuildCreatedAt:      "2023-01-10 13:50:02 UTC",
		BuildStartedAt:      "2023-01-10 13:50:05 UTC",
		BuildFinishedAt:     "2023-01-10 13:50:54 UTC",
		BuildDuration:       49.503592,
		BuildQueuedDuration: 0.193009,
		BuildAllowFailure:   false,
		BuildFailureReason:  "unknown_failure",
		RetriesCount:        1,
		PipelineID:          743121198,
		ProjectID:           31537070,
		ProjectName:         "John Smith / release-tools-fake",
		User: &EventUser{
			ID:        2967854,
			Name:      "John Smith",
			Username:  "jsmithy2",
			AvatarURL: "https://gitlab.com/uploads/-/system/user/avatar/2967852/avatar.png",
			Email:     "john@smith.com",
		},
		Repository: &Repository{
			Name:              "release-tools-fake",
			Description:       "",
			WebURL:            "",
			AvatarURL:         "",
			GitSSHURL:         "git@gitlab.com:jsmithy2/release-tools-fake.git",
			GitHTTPURL:        "https://gitlab.com/jsmithy2/release-tools-fake.git",
			Namespace:         "",
			Visibility:        "",
			PathWithNamespace: "",
			DefaultBranch:     "",
			Homepage:          "https://gitlab.com/jsmithy2/release-tools-fake",
			URL:               "git@gitlab.com:jsmithy2/release-tools-fake.git",
			SSHURL:            "",
			HTTPURL:           "",
		},
	}
	expectedEvent.Commit.ID = 743121198
	expectedEvent.Commit.Name = "Build pipeline"
	expectedEvent.Commit.SHA = "95d49d1efbd941908580e79d65e4b5ecaf4a8305"
	expectedEvent.Commit.Message = "Remove test jobs and add back other jobs"
	expectedEvent.Commit.AuthorName = "John Smith"
	expectedEvent.Commit.AuthorEmail = "john@smith.com"
	expectedEvent.Commit.AuthorURL = "https://gitlab.com/jsmithy2"
	expectedEvent.Commit.Status = "running"
	expectedEvent.Commit.Duration = 128
	expectedEvent.Commit.StartedAt = "2023-01-10 13:50:05 UTC"
	expectedEvent.Commit.FinishedAt = "2022-10-12 08:09:29 UTC"

	expectedEvent.Runner.ID = 12270837
	expectedEvent.Runner.Description = "4-blue.shared.runners-manager.gitlab.com/default"
	expectedEvent.Runner.RunnerType = "instance_type"
	expectedEvent.Runner.Active = true
	expectedEvent.Runner.IsShared = true
	expectedEvent.Runner.Tags = []string{"linux", "docker"}

	expectedEvent.Environment.Name = "production"
	expectedEvent.Environment.Action = "start"
	expectedEvent.Environment.DeploymentTier = "production"

	expectedEvent.SourcePipeline.Project.ID = 41
	expectedEvent.SourcePipeline.Project.WebURL = "https://gitlab.example.com/gitlab-org/upstream-project"
	expectedEvent.SourcePipeline.Project.PathWithNamespace = "gitlab-org/upstream-project"
	expectedEvent.SourcePipeline.PipelineID = 30
	expectedEvent.SourcePipeline.JobID = 3401

	assert.Equal(t, expectedEvent, *event, "event should be equal to the expected one")
}

func TestDeploymentEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/deployment.json")

	var event *DeploymentEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Deployment Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Deployment Event is null")
	}

	if event.Project.ID != 30 {
		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 30)
	}

	if event.User.ID != 42 {
		t.Errorf("User ID is %d, want %d", event.User.ID, 42)
	}

	if event.User.Name != expectedName {
		t.Errorf("Username is %s, want %s", event.User.Name, expectedName)
	}

	if event.CommitTitle != "Add new file" {
		t.Errorf("CommitTitle is %s, want %s", event.CommitTitle, "Add new file")
	}

	if event.Ref != "1.0.0" {
		t.Errorf("Ref is %s, want %s", event.Ref, "1.0.0")
	}

	if event.StatusChangedAt != "2021-04-28 21:50:00 +0200" {
		t.Errorf("StatusChangedAt is %s, want %s", event.StatusChangedAt, "2021-04-28 21:50:00 +0200")
	}

	if event.DeploymentID != 15 {
		t.Errorf("DeploymentID is %d, want %d", event.DeploymentID, 15)
	}

	if event.EnvironmentSlug != "staging" {
		t.Errorf("EnvironmentSlug is %s, want %s", event.EnvironmentSlug, "staging")
	}

	if event.EnvironmentExternalURL != "https://staging.example.com" {
		t.Errorf("EnvironmentExternalURL is %s, want %s", event.EnvironmentExternalURL, "https://staging.example.com")
	}
}

func TestFeatureFlagEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/feature_flag.json")

	var event *FeatureFlagEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("FeatureFlag Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("FeatureFlag Event is null")
	}

	if event.ObjectKind != "feature_flag" {
		t.Errorf("ObjectKind is %s, want %s", event.ObjectKind, "feature_flag")
	}

	if event.Project.ID != 1 {
		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 1)
	}

	if event.User.ID != 1 {
		t.Errorf("User ID is %d, want %d", event.User.ID, 1)
	}

	if event.User.Name != "Administrator" {
		t.Errorf("Username is %s, want %s", event.User.Name, "Administrator")
	}

	if event.ObjectAttributes.ID != 6 {
		t.Errorf("ObjectAttributes.ID is %d, want %d", event.ObjectAttributes.ID, 6)
	}

	if event.ObjectAttributes.Name != "test-feature-flag" {
		t.Errorf("ObjectAttributes.Name is %s, want %s", event.ObjectAttributes.Name, "test-feature-flag")
	}

	if event.ObjectAttributes.Description != "test-feature-flag-description" {
		t.Errorf("ObjectAttributes.Description is %s, want %s", event.ObjectAttributes.Description, "test-feature-flag-description")
	}

	if event.ObjectAttributes.Active != true {
		t.Errorf("ObjectAttributes.Active is %t, want %t", event.ObjectAttributes.Active, true)
	}
}

func TestGroupResourceAccessTokenEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/resource_access_token_group.json")
	var event *GroupResourceAccessTokenEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("could not unmarshal event: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("event is null")
	}

	expiresAt, err := ParseISOTime("2024-01-26")
	if err != nil {
		t.Fatalf("could not parse ISO time: %v", err)
	}

	expected := &GroupResourceAccessTokenEvent{
		ObjectKind: "access_token",
		EventName:  "expiring_access_token",
	}

	expected.Group.GroupID = 35
	expected.Group.GroupName = "Twitter"
	expected.Group.GroupPath = "twitter"
	expected.Group.FullPath = "twitter"

	expected.ObjectAttributes.ID = 25
	expected.ObjectAttributes.UserID = 90
	expected.ObjectAttributes.Name = "acd"
	expected.ObjectAttributes.CreatedAt = "2024-01-24 16:27:40 UTC"
	expected.ObjectAttributes.ExpiresAt = &expiresAt

	assert.Equal(t, expected, event)
}

func TestIssueCommentEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/note_issue.json")

	var event *IssueCommentEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Issue Comment Event can not unmarshaled: %v\n ", err.Error())
	}

	if event.ObjectKind != string(NoteEventTargetType) {
		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
	}

	if event.EventType != "note" {
		t.Errorf("EventType is %v, want %v", event.EventType, "note")
	}

	if event.ProjectID != 5 {
		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
	}

	if event.User.ID != 42 {
		t.Errorf("User ID is %d, want %d", event.User.ID, 42)
	}

	if event.ObjectAttributes.NoteableType != "Issue" {
		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Issue")
	}

	if event.ObjectAttributes.Action != CommentEventActionCreate {
		t.Errorf("Action is %v, want %v", event.ObjectAttributes.Action, "create")
	}

	if event.Issue.Title != "test_issue" {
		t.Errorf("Issue title is %v, want %v", event.Issue.Title, "test_issue")
	}

	if event.Issue.Position != 0 {
		t.Errorf("Issue position is %v, want %v", event.Issue.Position, 0)
	}

	if event.Issue.BranchName != "" {
		t.Errorf("Issue branch name is %v, want %v", event.Issue.BranchName, "")
	}

	if len(event.Issue.Labels) == 0 || event.Issue.Labels[0].ID != 25 {
		t.Errorf("Label id is null")
	}

	assert.Equal(t, []*EventLabel{
		{
			ID:          25,
			Title:       "Afterpod",
			Color:       "#3e8068",
			ProjectID:   0,
			CreatedAt:   "2019-06-05T14:32:20.211Z",
			UpdatedAt:   "2019-06-05T14:32:20.211Z",
			Template:    false,
			Description: "",
			Type:        "GroupLabel",
			GroupID:     4,
		},
		{
			ID:          86,
			Title:       "Element",
			Color:       "#231afe",
			ProjectID:   4,
			CreatedAt:   "2019-06-05T14:32:20.637Z",
			UpdatedAt:   "2019-06-05T14:32:20.637Z",
			Template:    false,
			Description: "",
			Type:        "ProjectLabel",
			GroupID:     0,
		},
	}, event.Issue.Labels)
}

func TestIssueEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/issue.json")

	var event *IssueEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Issue Event can not unmarshaled: %v\n ", err.Error())
	}

	if event.ObjectKind != string(IssueEventTargetType) {
		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, IssueEventTargetType)
	}

	if event.EventType != "issue" {
		t.Errorf("EventType is %v, want %v", event.EventType, "issue")
	}

	if event.Project.ID != 1 {
		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 1)
	}

	if event.User.ID != 1 {
		t.Errorf("User ID is %d, want %d", event.User.ID, 1)
	}

	if event.Assignee.Username != "user1" {
		t.Errorf("Assignee username is %s, want %s", event.Assignee.Username, "user1")
	}

	if event.ObjectAttributes.ID != 301 {
		t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 301)
	}

	if event.ObjectAttributes.Title != "New API: create/update/delete file" {
		t.Errorf("ObjectAttributes.Title is %v, want %v", event.ObjectAttributes.Title, "New API: create/update/delete file")
	}

	if event.ObjectAttributes.StateID != StateIDOpen {
		t.Errorf("ObjectAttributes.StateID is %v, want %v", event.ObjectAttributes.StateID, StateIDOpen)
	}

	if event.ObjectAttributes.State != "opened" {
		t.Errorf("ObjectAttributes.State is %v, want %v", event.ObjectAttributes.State, "opened")
	}
	if event.ObjectAttributes.Confidential != false {
		t.Errorf("ObjectAttributes.Confidential is %v, want %v", event.ObjectAttributes.Confidential, false)
	}

	if event.ObjectAttributes.TotalTimeSpent != 0 {
		t.Errorf("ObjectAttributes.TotalTimeSpent is %v, want %v", event.ObjectAttributes.TotalTimeSpent, 0)
	}

	if event.ObjectAttributes.Action != "open" {
		t.Errorf("ObjectAttributes.Action is %v, want %v", event.ObjectAttributes.Action, "open")
	}

	if event.ObjectAttributes.EscalationStatus != "triggered" {
		t.Errorf("ObjectAttributes.EscalationStatus is %v, want %v", event.ObjectAttributes.EscalationStatus, "triggered")
	}

	if event.ObjectAttributes.EscalationPolicy.ID != 18 {
		t.Errorf("ObjectAttributes.EscalationPolicy.ID is %v, want %v", event.ObjectAttributes.EscalationPolicy.ID, 18)
	}

	if event.Changes.TotalTimeSpent.Previous != 8100 {
		t.Errorf("Changes.TotalTimeSpent.Previous is %v , want %v", event.Changes.TotalTimeSpent.Previous, 8100)
	}

	if event.Changes.TotalTimeSpent.Current != 9900 {
		t.Errorf("Changes.TotalTimeSpent.Current is %v , want %v", event.Changes.TotalTimeSpent.Current, 8100)
	}

	assert.Equal(t, []*EventLabel{
		{
			ID:          206,
			Title:       "API",
			Color:       "#ffffff",
			ProjectID:   14,
			CreatedAt:   "2013-12-03T17:15:43Z",
			UpdatedAt:   "2013-12-03T17:15:43Z",
			Template:    false,
			Description: "API related issues",
			Type:        "ProjectLabel",
			GroupID:     41,
		},
	}, event.Labels)

	assert.Equal(t, []*EventLabel{
		{
			ID:          206,
			Title:       "API",
			Color:       "#ffffff",
			ProjectID:   14,
			CreatedAt:   "2013-12-03T17:15:43Z",
			UpdatedAt:   "2013-12-03T17:15:43Z",
			Template:    false,
			Description: "API related issues",
			Type:        "ProjectLabel",
			GroupID:     41,
		},
	}, event.Changes.Labels.Previous)

	assert.Equal(t, []*EventLabel{
		{
			ID:          205,
			Title:       "Platform",
			Color:       "#123123",
			ProjectID:   14,
			CreatedAt:   "2013-12-03T17:15:43Z",
			UpdatedAt:   "2013-12-03T17:15:43Z",
			Template:    false,
			Description: "Platform related issues",
			Type:        "ProjectLabel",
			GroupID:     41,
		},
	}, event.Changes.Labels.Current)

	assert.Equal(t, "2017-09-15 16:54:55 UTC", event.Changes.ClosedAt.Previous)
	assert.Equal(t, "2017-09-15 16:56:00 UTC", event.Changes.ClosedAt.Current)

	assert.Equal(t, StateIDNone, event.Changes.StateID.Previous)
	assert.Equal(t, StateIDOpen, event.Changes.StateID.Current)

	assert.Equal(t, "2017-09-15 16:50:55 UTC", event.Changes.UpdatedAt.Previous)
	assert.Equal(t, "2017-09-15 16:52:00 UTC", event.Changes.UpdatedAt.Current)
}

// Generate unit test for MergeCommentEvent
func TestMergeCommentEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/note_merge_request.json")

	var event *MergeCommentEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Merge Comment Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Merge Comment Event is null")
	}

	if event.ObjectAttributes.ID != 1244 {
		t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 1244)
	}

	if event.ObjectAttributes.Note != "This MR needs work." {
		t.Errorf("ObjectAttributes.Note is %v, want %v", event.ObjectAttributes.Note, "This MR needs work.")
	}

	if event.ObjectAttributes.NoteableType != "MergeRequest" {
		t.Errorf("ObjectAttributes.NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "MergeRequest")
	}

	if event.ObjectAttributes.Action != CommentEventActionCreate {
		t.Errorf("Action is %v, want %v", event.ObjectAttributes.Action, "create")
	}

	if event.ObjectAttributes.AuthorID != 1 {
		t.Errorf("ObjectAttributes.AuthorID is %v, want %v", event.ObjectAttributes.AuthorID, 1)
	}

	if event.ObjectAttributes.CreatedAt != "2015-05-17 18:21:36 UTC" {
		t.Errorf("ObjectAttributes.CreatedAt is %v, want %v", event.ObjectAttributes.CreatedAt, "2015-05-17 18:21:36 UTC")
	}

	if event.ObjectAttributes.UpdatedAt != "2015-05-17 18:21:36 UTC" {
		t.Errorf("ObjectAttributes.UpdatedAt is %v, want %v", event.ObjectAttributes.UpdatedAt, "2015-05-17 18:21:36 UTC")
	}

	if event.ObjectAttributes.ProjectID != 5 {
		t.Errorf("ObjectAttributes.ProjectID is %v, want %v", event.ObjectAttributes.ProjectID, 5)
	}

	if event.MergeRequest.ID != 7 {
		t.Errorf("MergeRequest.ID is %v, want %v", event.MergeRequest.ID, 7)
	}

	if event.MergeRequest.TargetBranch != "markdown" {
		t.Errorf("MergeRequest.TargetBranch is %v, want %v", event.MergeRequest.TargetBranch, "markdown")
	}

	// generate test code for rest of the event.MergeRequest fields
	if event.MergeRequest.SourceBranch != "master" {
		t.Errorf("MergeRequest.SourceBranch is %v, want %v", event.MergeRequest.SourceBranch, "ms-viewport")
	}

	if event.MergeRequest.SourceProjectID != 5 {
		t.Errorf("MergeRequest.SourceProjectID is %v, want %v", event.MergeRequest.SourceProjectID, 5)
	}

	if event.MergeRequest.AuthorID != 8 {
		t.Errorf("MergeRequest.AuthorID is %v, want %v", event.MergeRequest.AuthorID, 8)
	}

	if event.MergeRequest.AssigneeID != 28 {
		t.Errorf("MergeRequest.AssigneeID is %v, want %v", event.MergeRequest.AssigneeID, 28)
	}

	if len(event.MergeRequest.AssigneeIDs) < 1 {
		t.Errorf("MergeRequest.AssigneeIDs length is %d, want %d", len(event.MergeRequest.AssigneeIDs), 1)
	}

	if event.MergeRequest.AssigneeIDs[0] != 28 {
		t.Errorf("MergeRequest.AssigneeIDs[0] is %v, want %v", event.MergeRequest.AssigneeIDs[0], 28)
	}

	if len(event.MergeRequest.ReviewerIDs) < 1 {
		t.Errorf("MergeRequest.ReviewerIDs length is %d, want %d", len(event.MergeRequest.ReviewerIDs), 1)
	}

	if event.MergeRequest.ReviewerIDs[0] != 13 {
		t.Errorf("MergeRequest.ReviewerIDs[0] is %v, want %v", event.MergeRequest.ReviewerIDs[0], 13)
	}

	if event.MergeRequest.State != "opened" {
		t.Errorf("MergeRequest.state is %v, want %v", event.MergeRequest.State, "opened")
	}

	if event.MergeRequest.MergeStatus != "cannot_be_merged" {
		t.Errorf("MergeRequest.merge_status is %v, want %v", event.MergeRequest.MergeStatus, "cannot_be_merged")
	}

	if event.MergeRequest.TargetProjectID != 5 {
		t.Errorf("MergeRequest.target_project_id is %v, want %v", event.MergeRequest.TargetProjectID, 5)
	}

	assert.Equal(t, []*EventLabel{
		{
			ID:          206,
			Title:       "Afterpod",
			Color:       "#3e8068",
			ProjectID:   0,
			CreatedAt:   "2019-06-05T14:32:20.211Z",
			UpdatedAt:   "2019-06-05T14:32:20.211Z",
			Template:    false,
			Description: "",
			Type:        "GroupLabel",
			GroupID:     4,
		},
		{
			ID:          86,
			Title:       "Element",
			Color:       "#231afe",
			ProjectID:   4,
			CreatedAt:   "2019-06-05T14:32:20.637Z",
			UpdatedAt:   "2019-06-05T14:32:20.637Z",
			Template:    false,
			Description: "",
			Type:        "ProjectLabel",
			GroupID:     0,
		},
	}, event.MergeRequest.Labels)

	assert.Equal(t, &EventUser{
		ID:        0,
		Name:      "User1",
		Username:  "user1",
		AvatarURL: "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon",
		Email:     "",
	}, event.MergeRequest.Assignee)

	if event.MergeRequest.DetailedMergeStatus != "checking" {
		t.Errorf("MergeRequest.DetailedMergeStatus is %v, want %v", event.MergeRequest.DetailedMergeStatus, "checking")
	}

	if event.MergeRequest.URL != "http://example.com/gitlab-org/gitlab-test/-/merge_requests/1" {
		t.Errorf("MergeRequest.URL is %v, want %v", event.MergeRequest.URL, "http://example.com/gitlab-org/gitlab-test/-/merge_requests/1")
	}
}

func TestMergeEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/merge_request.json")

	var event *MergeEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Merge Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Merge Event is null")
	}

	if event.EventType != "merge_request" {
		t.Errorf("EventType is %v, want %v", event.EventType, "merge_request")
	}

	if event.Project.CIConfigPath != "" {
		t.Errorf("Project.CIConfigPath is %v, want %v", event.Project.CIConfigPath, "")
	}

	if event.ObjectAttributes.ID != 99 {
		t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 99)
	}

	if event.ObjectAttributes.Source.Homepage != "http://example.com/awesome_space/awesome_project" {
		t.Errorf("ObjectAttributes.Source.Homepage is %v, want %v", event.ObjectAttributes.Source.Homepage, "http://example.com/awesome_space/awesome_project")
	}

	if event.ObjectAttributes.LastCommit.ID != "da1560886d4f094c3e6c9ef40349f7d38b5d27d7" {
		t.Errorf("ObjectAttributes.LastCommit.ID is %v, want %s", event.ObjectAttributes.LastCommit.ID, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
	}

	if event.ObjectAttributes.TotalTimeSpent != 0 {
		t.Errorf("ObjectAttributes.TotalTimeSpent is %v, want %v", event.ObjectAttributes.TotalTimeSpent, 0)
	}

	if event.ObjectAttributes.TimeChange != 0 {
		t.Errorf("ObjectAttributes.TimeChange is %v, want %v", event.ObjectAttributes.TimeChange, 0)
	}

	if event.ObjectAttributes.HumanTotalTimeSpent != "30m" {
		t.Errorf("ObjectAttributes.HumanTotalTimeSpent is %v, want %v", event.ObjectAttributes.HumanTotalTimeSpent, "30m")
	}

	if event.ObjectAttributes.HumanTimeChange != "30m" {
		t.Errorf("ObjectAttributes.HumanTimeChange is %v, want %v", event.ObjectAttributes.HumanTimeChange, "30m")
	}

	if event.ObjectAttributes.HumanTimeEstimate != "1h" {
		t.Errorf("ObjectAttributes.HumanTimeEstimate is %v, want %v", event.ObjectAttributes.HumanTimeEstimate, "1h")
	}

	if event.Assignees[0].Name != expectedName {
		t.Errorf("Assignee.Name is %v, want %v", event.Assignees[0].Name, expectedName)
	}

	if event.Assignees[0].Username != expectedUsername {
		t.Errorf("ObjectAttributes is %v, want %v", event.Assignees[0].Username, expectedUsername)
	}

	if event.User.ID != expectedID {
		t.Errorf("User ID is %d, want %d", event.User.ID, expectedID)
	}

	if event.User.Name != expectedName {
		t.Errorf("Username is %s, want %s", event.User.Name, expectedName)
	}

	if event.User.Email != expectedEmail {
		t.Errorf("User email is %s, want %s", event.User.Email, expectedEmail)
	}

	if event.ObjectAttributes.LastCommit.Timestamp == nil {
		t.Errorf("Timestamp isn't nil")
	}

	if name := event.ObjectAttributes.LastCommit.Author.Name; name != "GitLab dev user" {
		t.Errorf("Commit Username is %s, want %s", name, "GitLab dev user")
	}

	if event.ObjectAttributes.BlockingDiscussionsResolved != true {
		t.Errorf("BlockingDiscussionsResolved isn't true")
	}

	if event.ObjectAttributes.FirstContribution != true {
		t.Errorf("FirstContribution isn't true")
	}

	if event.Assignees[0].ID != expectedID {
		t.Errorf("Assignees[0].ID is %v, want %v", event.Assignees[0].ID, expectedID)
	}

	if event.Assignees[0].Name != expectedName {
		t.Errorf("Assignees[0].Name is %v, want %v", event.Assignees[0].Name, expectedName)
	}

	if event.Assignees[0].Username != expectedUsername {
		t.Errorf("Assignees[0].Username is %v, want %v", event.Assignees[0].Username, expectedName)
	}

	if event.Assignees[0].AvatarURL != expectedAvatar {
		t.Errorf("Assignees[0].AvatarURL is %v, want %v", event.Assignees[0].AvatarURL, expectedAvatar)
	}

	if len(event.Reviewers) < 1 {
		t.Errorf("Reviewers length is %d, want %d", len(event.Reviewers), 1)
	}

	if event.Reviewers[0].Name != expectedName {
		t.Errorf("Reviewers[0].Name is %v, want %v", event.Reviewers[0].Name, expectedName)
	}

	if event.Reviewers[0].Username != expectedUsername {
		t.Errorf("Reviewer[0].Username is %v, want %v", event.Reviewers[0].Username, expectedUsername)
	}

	if event.Reviewers[0].AvatarURL != expectedAvatar {
		t.Errorf("Reviewers[0].AvatarURL is %v, want %v", event.Reviewers[0].AvatarURL, expectedAvatar)
	}

	if event.ObjectAttributes.DetailedMergeStatus != "mergeable" {
		t.Errorf("DetailedMergeStatus is %s, want %s", event.ObjectAttributes.DetailedMergeStatus, "mergeable")
	}

	assert.Equal(t, []*EventLabel{
		{
			ID:          206,
			Title:       "API",
			Color:       "#ffffff",
			ProjectID:   14,
			CreatedAt:   "2013-12-03T17:15:43Z",
			UpdatedAt:   "2013-12-03T17:15:43Z",
			Template:    false,
			Description: "API related issues",
			Type:        "ProjectLabel",
			GroupID:     41,
		},
	}, event.Labels)

	assert.Equal(t, []*EventLabel{
		{
			ID:          206,
			Title:       "API",
			Color:       "#ffffff",
			ProjectID:   14,
			CreatedAt:   "2013-12-03T17:15:43Z",
			UpdatedAt:   "2013-12-03T17:15:43Z",
			Template:    false,
			Description: "API related issues",
			Type:        "ProjectLabel",
			GroupID:     41,
		},
	}, event.ObjectAttributes.Labels)

	assert.Equal(t, []*EventLabel{
		{
			ID:          206,
			Title:       "API",
			Color:       "#ffffff",
			ProjectID:   14,
			CreatedAt:   "2013-12-03T17:15:43Z",
			UpdatedAt:   "2013-12-03T17:15:43Z",
			Template:    false,
			Description: "API related issues",
			Type:        "ProjectLabel",
			GroupID:     41,
		},
	}, event.Changes.Labels.Previous)

	assert.Equal(t, []*EventLabel{
		{
			ID:          205,
			Title:       "Platform",
			Color:       "#123123",
			ProjectID:   14,
			CreatedAt:   "2013-12-03T17:15:43Z",
			UpdatedAt:   "2013-12-03T17:15:43Z",
			Template:    false,
			Description: "Platform related issues",
			Type:        "ProjectLabel",
			GroupID:     41,
		},
	}, event.Changes.Labels.Current)

	assert.Equal(t, StateIDLocked, event.Changes.StateID.Previous)
	assert.Equal(t, StateIDMerged, event.Changes.StateID.Current)

	assert.Equal(t, "2017-09-15 16:50:55 UTC", event.Changes.UpdatedAt.Previous)
	assert.Equal(t, "2017-09-15 16:52:00 UTC", event.Changes.UpdatedAt.Current)

	assert.True(t, event.ObjectAttributes.System)
	assert.Equal(t, "code_owner_approvals_reset_on_push", event.ObjectAttributes.SystemAction)
}

func TestMemberEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/member.json")

	var event *MemberEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Member Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Member Event is null")
	}

	if event.GroupName != ExpectedGroup {
		t.Errorf("Name is %v, want %v", event.GroupName, ExpectedGroup)
	}

	if event.GroupPath != ExpectedGroup {
		t.Errorf("GroupPath is %v, want %v", event.GroupPath, ExpectedGroup)
	}

	if event.GroupID != 100 {
		t.Errorf(
			"GroupID is %v, want %v", event.GroupID, 100)
	}

	if event.UserUsername != expectedUsername {
		t.Errorf(
			"UserUsername is %v, want %v", event.UserUsername, expectedUsername)
	}

	if event.UserName != expectedName {
		t.Errorf(
			"UserName is %v, want %v", event.UserName, expectedName)
	}

	if event.UserEmail != "testuser@webhooktest.com" {
		t.Errorf(
			"UserEmail is %v, want %v", event.UserEmail, "testuser@webhooktest.com")
	}

	if event.UserID != 64 {
		t.Errorf(
			"UserID is %v, want %v", event.UserID, 64)
	}

	if event.GroupAccess != "Guest" {
		t.Errorf(
			"GroupAccess is %v, want %v", event.GroupAccess, "Guest")
	}

	if event.EventName != expectedEventName {
		t.Errorf(
			"EventName is %v, want %v", event.EventName, expectedEventName)
	}

	if event.CreatedAt.Format(time.RFC3339) != "2020-12-11T04:57:22Z" {
		t.Errorf("CreatedAt is %v, want %v", event.CreatedAt.Format(time.RFC3339), "2020-12-11T04:57:22Z")
	}

	if event.UpdatedAt.Format(time.RFC3339) != "2020-12-11T04:57:22Z" {
		t.Errorf("UpdatedAt is %v, want %v", event.UpdatedAt.Format(time.RFC3339), "2020-12-11T04:57:22Z")
	}

	if event.ExpiresAt.Format(time.RFC3339) != "2020-12-14T00:00:00Z" {
		t.Errorf("ExpiresAt is %v, want %v", event.ExpiresAt.Format(time.RFC3339), "2020-12-14T00:00:00Z")
	}
}

func TestMergeEventUnmarshalFromGroup(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/group_merge_request.json")

	var event *MergeEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Group Merge Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Group Merge Event is null")
	}

	if event.ObjectKind != eventObjectKindMergeRequest {
		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, eventObjectKindMergeRequest)
	}

	if event.User.Username != expectedUsername {
		t.Errorf("User.Username is %v, want %v", event.User.Username, expectedUsername)
	}

	if event.Project.Name != exampleProjectName {
		t.Errorf("Project.Name is %v, want %v", event.Project.Name, exampleProjectName)
	}

	if event.ObjectAttributes.ID != 15917 {
		t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 15917)
	}

	if event.ObjectAttributes.Source.Name != exampleProjectName {
		t.Errorf("ObjectAttributes.Source.Name is %v, want %v", event.ObjectAttributes.Source.Name, exampleProjectName)
	}

	if event.ObjectAttributes.LastCommit.Author.Email != "test.user@mail.com" {
		t.Errorf("ObjectAttributes.LastCommit.Author.Email is %v, want %v", event.ObjectAttributes.LastCommit.Author.Email, "test.user@mail.com")
	}

	if event.Repository.Name != exampleProjectName {
		t.Errorf("Repository.Name is %v, want %v", event.Repository.Name, exampleProjectName)
	}

	if event.User.Name != expectedName {
		t.Errorf("Username is %s, want %s", event.User.Name, expectedName)
	}

	if event.ObjectAttributes.LastCommit.Timestamp == nil {
		t.Errorf("Timestamp isn't nil")
	}

	if name := event.ObjectAttributes.LastCommit.Author.Name; name != "Test User" {
		t.Errorf("Commit Username is %s, want %s", name, "Test User")
	}
}

func TestPipelineEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/pipeline.json")

	var event *PipelineEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Pipeline Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Pipeline Event is null")
	}

	if event.ObjectAttributes.ID != 31 {
		t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 31)
	}

	if event.ObjectAttributes.IID != 123 {
		t.Errorf("ObjectAttributes.IID is %v, want %v", event.ObjectAttributes.ID, 123)
	}

	if event.ObjectAttributes.DetailedStatus != "passed" {
		t.Errorf("ObjectAttributes.DetailedStatus is %v, want %v", event.ObjectAttributes.DetailedStatus, "passed")
	}

	if event.ObjectAttributes.QueuedDuration != 12 {
		t.Errorf("ObjectAttributes.QueuedDuration is %v, want %v", event.ObjectAttributes.QueuedDuration, 12)
	}

	if event.ObjectAttributes.Variables[0].Key != "NESTOR_PROD_ENVIRONMENT" {
		t.Errorf("ObjectAttributes.Variables[0].Key is %v, want %v", event.ObjectAttributes.Variables[0].Key, "NESTOR_PROD_ENVIRONMENT")
	}

	if event.User.ID != 42 {
		t.Errorf("User ID is %d, want %d", event.User.ID, 42)
	}

	if event.User.Name != expectedName {
		t.Errorf("Username is %s, want %s", event.User.Name, expectedName)
	}

	if event.Commit.Timestamp == nil {
		t.Errorf("Timestamp isn't nil")
	}

	if name := event.Commit.Author.Name; name != "User" {
		t.Errorf("Commit Username is %s, want %s", name, "User")
	}

	if len(event.Builds) != 5 {
		t.Errorf("Builds length is %d, want %d", len(event.Builds), 5)
	}

	if event.Builds[0].AllowFailure != true {
		t.Errorf("Builds.0.AllowFailure is %v, want %v", event.Builds[0].AllowFailure, true)
	}

	if event.Builds[0].Environment.Name != "production" {
		t.Errorf("Builds.0.Environment.Name is %v, want %v", event.Builds[0].Environment.Name, "production")
	}

	if event.Builds[0].Duration != 17.1 {
		t.Errorf("Builds[0].Duration is %v, want %v", event.Builds[0].Duration, 17.1)
	}

	if event.Builds[0].QueuedDuration != 3.5 {
		t.Errorf("Builds[0].QueuedDuration is %v, want %v", event.Builds[0].QueuedDuration, 3.5)
	}

	if event.Builds[0].FailureReason != "script_failure" {
		t.Errorf("Builds[0].Failurereason is %v, want %v", event.Builds[0].FailureReason, "script_failure")
	}

	if event.Builds[1].FailureReason != "" {
		t.Errorf("Builds[0].Failurereason is %v, want %v", event.Builds[0].FailureReason, "''")
	}

	if event.SourcePipeline.PipelineID != 30 {
		t.Errorf("Source Pipeline ID is %v, want %v", event.SourcePipeline.PipelineID, 30)
	}

	if event.SourcePipeline.JobID != 3401 {
		t.Errorf("Source Pipeline JobID is %v, want %v", event.SourcePipeline.JobID, 3401)
	}

	if event.SourcePipeline.Project.ID != 41 {
		t.Errorf("Source Pipeline Project ID is %v, want %v", event.SourcePipeline.Project.ID, 41)
	}

	if event.MergeRequest.DetailedMergeStatus != "mergeable" {
		t.Errorf("MergeRequest.DetailedMergeStatus is %v, want %v", event.MergeRequest.DetailedMergeStatus, "mergeable")
	}
}

func TestProjectResourceAccessTokenEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/resource_access_token_project.json")
	var event *ProjectResourceAccessTokenEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("could not unmarshal event: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("event is null")
	}

	expiresAt, err := ParseISOTime("2024-01-26")
	if err != nil {
		t.Fatalf("could not parse ISO time: %v", err)
	}

	expected := &ProjectResourceAccessTokenEvent{
		ObjectKind: "access_token",
		EventName:  "expiring_access_token",
	}

	expected.ObjectAttributes.ID = 25
	expected.ObjectAttributes.UserID = 90
	expected.ObjectAttributes.Name = "acd"
	expected.ObjectAttributes.CreatedAt = "2024-01-24 16:27:40 UTC"
	expected.ObjectAttributes.ExpiresAt = &expiresAt

	expected.Project.ID = 7
	expected.Project.Name = "Flight"
	expected.Project.Description = "Eum dolore maxime atque reprehenderit voluptatem."
	expected.Project.WebURL = "https://example.com/flightjs/Flight"
	expected.Project.AvatarURL = ""
	expected.Project.GitSSHURL = "ssh://git@example.com/flightjs/Flight.git"
	expected.Project.GitHTTPURL = "https://example.com/flightjs/Flight.git"
	expected.Project.Namespace = "Flightjs"
	expected.Project.VisibilityLevel = 0
	expected.Project.PathWithNamespace = "flightjs/Flight"
	expected.Project.DefaultBranch = "master"
	expected.Project.CIConfigPath = ""
	expected.Project.Homepage = "https://example.com/flightjs/Flight"
	expected.Project.URL = "ssh://git@example.com/flightjs/Flight.git"
	expected.Project.SSHURL = "ssh://git@example.com/flightjs/Flight.git"
	expected.Project.HTTPURL = "https://example.com/flightjs/Flight.git"

	assert.Equal(t, expected, event)
}

func TestPushEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/push.json")
	var event *PushEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Push Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Push Event is null")
	}

	if event.EventName != "push" {
		t.Errorf("EventName is %v, want %v", event.EventName, "push")
	}

	if event.ProjectID != 15 {
		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 15)
	}

	if event.UserName != exampleEventUserName {
		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
	}

	if event.Project.ID != 15 {
		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 15)
	}

	if !event.RefProtected {
		t.Errorf("RefProtected is %v, want %v", event.RefProtected, true)
	}

	if event.Commits[0] == nil || event.Commits[0].Timestamp == nil {
		t.Errorf("Commit Timestamp isn't nil")
	}

	if event.Commits[0] == nil || event.Commits[0].Message != exampleCommitMessage {
		t.Errorf("Commit Message is %s, want %s", event.Commits[0].Message, exampleCommitMessage)
	}

	if event.Commits[0] == nil || event.Commits[0].Title != exampleCommitTitle {
		t.Errorf("Commit Title is %s, want %s", event.Commits[0].Title, exampleCommitTitle)
	}

	if event.Commits[0] == nil || event.Commits[0].Author.Name != "Jordi Mallach" {
		t.Errorf("Commit Username is %s, want %s", event.UserName, "Jordi Mallach")
	}
}

func TestReleaseEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/release.json")

	var event *ReleaseEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Release Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Release Event is null")
	}

	if event.Project.ID != 327622 {
		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 327622)
	}

	if event.Commit.Title != "Merge branch 'example-branch' into 'master'" {
		t.Errorf("Commit title is %s, want %s", event.Commit.Title, "Merge branch 'example-branch' into 'master'")
	}

	if len(event.Assets.Sources) != 4 {
		t.Errorf("Asset sources length is %d, want %d", len(event.Assets.Sources), 4)
	}

	if event.Assets.Sources[0].Format != "zip" {
		t.Errorf("First asset source format is %s, want %s", event.Assets.Sources[0].Format, "zip")
	}

	if len(event.Assets.Links) != 1 {
		t.Errorf("Asset links length is %d, want %d", len(event.Assets.Links), 1)
	}

	if event.Assets.Links[0].Name != "Changelog" {
		t.Errorf("First asset link name is %s, want %s", event.Assets.Links[0].Name, "Changelog")
	}

	if event.Commit.Author.Name != "User" {
		t.Errorf("Commit author name is %s, want %s", event.Commit.Author.Name, "User")
	}
}

func TestSubGroupEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/subgroup.json")

	var event *SubGroupEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("SubGroup Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("SubGroup Event is null")
	}

	if event.Name != "SubGroup 1" {
		t.Errorf("Name is %v, want %v", event.Name, "SubGroup 1")
	}

	if event.GroupID != 2 {
		t.Errorf("GroupID is %v, want %v", event.GroupID, 2)
	}

	if event.ParentGroupID != 1 {
		t.Errorf("ParentGroupID is %v, want %v", event.ParentGroupID, 1)
	}

	if event.CreatedAt.Format(time.RFC3339) != "2022-01-24T14:23:59Z" {
		t.Errorf("CreatedAt is %v, want %v", event.CreatedAt.Format(time.RFC3339), "2022-01-24T14:23:59Z")
	}
}

func TestTagEventUnmarshal(t *testing.T) {
	t.Parallel()
	jsonObject := loadFixture(t, "testdata/webhooks/tag_push.json")
	var event *TagEvent
	err := json.Unmarshal(jsonObject, &event)
	if err != nil {
		t.Errorf("Tag Event can not unmarshaled: %v\n ", err.Error())
	}

	if event == nil {
		t.Errorf("Tag Event is null")
	}

	if event.EventName != "tag_push" {
		t.Errorf("EventName is %v, want %v", event.EventName, "tag_push")
	}

	if event.ProjectID != 1 {
		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 1)
	}

	if event.Project.ID != 1 {
		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 1)
	}

	if event.UserName != exampleEventUserName {
		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
	}

	if event.Commits[0] == nil || event.Commits[0].Timestamp == nil {
		t.Errorf("Commit Timestamp isn't nil")
	}

	if event.Commits[0] == nil || event.Commits[0].Message != exampleCommitMessage {
		t.Errorf("Commit Message is %s, want %s", event.Commits[0].Message, exampleCommitMessage)
	}

	if event.Commits[0] == nil || event.Commits[0].Title != exampleCommitTitle {
		t.Errorf("Commit Title is %s, want %s", event.Commits[0].Title, exampleCommitTitle)
	}

	if event.Commits[0] == nil || event.Commits[0].Author.Name != exampleEventUserName {
		t.Errorf("Commit Username is %s, want %s", event.UserName, exampleEventUserName)
	}
}

func TestSnippetCommentEventUnmarshal(t *testing.T) {
	t.Parallel()

	// GIVEN a snippet comment event JSON payload
	jsonObject := loadFixture(t, "testdata/webhooks/note_snippet.json")

	// WHEN the JSON is unmarshaled
	var event *SnippetCommentEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "note", event.ObjectKind)
	assert.Equal(t, "note", event.EventType)
	assert.Equal(t, int64(5), event.ProjectID)

	// User assertions
	assert.Equal(t, int64(42), event.User.ID)
	assert.Equal(t, "User1", event.User.Name)
	assert.Equal(t, "user1", event.User.Username)

	// Project assertions
	assert.Equal(t, "Gitlab Test", event.Project.Name)
	assert.Equal(t, "gitlab-org/gitlab-test", event.Project.PathWithNamespace)

	// Repository assertions
	assert.Equal(t, "Gitlab Test", event.Repository.Name)

	// Object attributes assertions
	assert.Equal(t, int64(1245), event.ObjectAttributes.ID)
	assert.Equal(t, "Is this snippet doing what it's supposed to be doing?", event.ObjectAttributes.Note)
	assert.Equal(t, "Snippet", event.ObjectAttributes.NoteableType)
	assert.Equal(t, int64(1), event.ObjectAttributes.AuthorID)
	assert.Equal(t, int64(53), event.ObjectAttributes.NoteableID)
	assert.Equal(t, CommentEventActionCreate, event.ObjectAttributes.Action)
	assert.Equal(t, "http://example.com/gitlab-org/gitlab-test/snippets/53#note_1245", event.ObjectAttributes.URL)

	// Snippet assertions
	assert.NotNil(t, event.Snippet)
	assert.Equal(t, int64(53), event.Snippet.ID)
	assert.Equal(t, "test", event.Snippet.Title)
	assert.Equal(t, "puts 'Hello world'", event.Snippet.Content)
	assert.Equal(t, int64(1), event.Snippet.AuthorID)
	assert.Equal(t, int64(5), event.Snippet.ProjectID)
	assert.Equal(t, "test.rb", event.Snippet.Filename)
	assert.Equal(t, "ProjectSnippet", event.Snippet.Type)
	assert.Equal(t, int64(0), event.Snippet.VisibilityLevel)
	assert.Equal(t, "Prints 'Hello world'", event.Snippet.Description)
	assert.False(t, event.Snippet.Secret)
	assert.False(t, event.Snippet.RepositoryReadOnly)
}

func TestWikiPageEventUnmarshal(t *testing.T) {
	t.Parallel()

	// GIVEN a wiki page event JSON payload
	jsonObject := loadFixture(t, "testdata/webhooks/wiki_page.json")

	// WHEN the JSON is unmarshaled
	var event *WikiPageEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "wiki_page", event.ObjectKind)

	// User assertions
	assert.Equal(t, "User1", event.User.Name)
	assert.Equal(t, "user1", event.User.Username)

	// Project assertions
	assert.Equal(t, "awesome-project", event.Project.Name)
	assert.Equal(t, "This is awesome", event.Project.Description)
	assert.Equal(t, "root/awesome-project", event.Project.PathWithNamespace)
	assert.Equal(t, "master", event.Project.DefaultBranch)

	// Wiki assertions
	assert.Equal(t, "http://example.com/root/awesome-project/wikis/home", event.Wiki.WebURL)
	assert.Equal(t, "git@example.com:root/awesome-project.wiki.git", event.Wiki.GitSSHURL)
	assert.Equal(t, "http://example.com/root/awesome-project.wiki.git", event.Wiki.GitHTTPURL)
	assert.Equal(t, "root/awesome-project.wiki", event.Wiki.PathWithNamespace)
	assert.Equal(t, "master", event.Wiki.DefaultBranch)

	// Object attributes assertions
	assert.Equal(t, "Awesome", event.ObjectAttributes.Title)
	assert.Equal(t, "awesome content goes here", event.ObjectAttributes.Content)
	assert.Equal(t, "markdown", event.ObjectAttributes.Format)
	assert.Equal(t, "adding an awesome page to the wiki", event.ObjectAttributes.Message)
	assert.Equal(t, "awesome", event.ObjectAttributes.Slug)
	assert.Equal(t, "http://example.com/root/awesome-project/wikis/awesome", event.ObjectAttributes.URL)
	assert.Equal(t, "create", event.ObjectAttributes.Action)
	assert.Equal(t, "http://example.com/root/awesome-project/wikis/awesome/diff", event.ObjectAttributes.DiffURL)
}

func TestEmojiEventUnmarshal(t *testing.T) {
	t.Parallel()

	// GIVEN an emoji event JSON payload
	jsonObject := loadFixture(t, "testdata/webhooks/emoji_issue.json")

	// WHEN the JSON is unmarshaled
	var event *EmojiEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "emoji", event.ObjectKind)
	assert.Equal(t, "award", event.EventType)
	assert.Equal(t, int64(7), event.ProjectID)

	// User assertions
	assert.Equal(t, int64(1), event.User.ID)
	assert.Equal(t, "Administrator", event.User.Name)
	assert.Equal(t, "root", event.User.Username)
	assert.Equal(t, "admin@example.com", event.User.Email)

	// Project assertions
	assert.NotNil(t, event.Project)
	assert.Equal(t, int64(7), event.Project.ID)
	assert.Equal(t, "Flight", event.Project.Name)
	assert.Equal(t, "flightjs/Flight", event.Project.PathWithNamespace)
	assert.Equal(t, int64(0), event.Project.VisibilityLevel)

	// Object attributes assertions
	assert.Equal(t, int64(42), event.ObjectAttributes.ID)
	assert.Equal(t, int64(1), event.ObjectAttributes.UserID)
	assert.Equal(t, "thumbsup", event.ObjectAttributes.Name)
	assert.Equal(t, "Issue", event.ObjectAttributes.AwardableType)
	assert.Equal(t, int64(123), event.ObjectAttributes.AwardableID)
	assert.Equal(t, "award", event.ObjectAttributes.Action)
	assert.Equal(t, "https://example.com/flightjs/Flight/-/issues/1", event.ObjectAttributes.AwardedOnURL)

	// Issue assertions
	assert.NotNil(t, event.Issue)
	assert.Equal(t, int64(123), event.Issue.ID)
	assert.Equal(t, int64(1), event.Issue.IID)
	assert.Equal(t, int64(7), event.Issue.ProjectID)
	assert.Equal(t, "Test Issue", event.Issue.Title)
	assert.Equal(t, "This is a test issue", event.Issue.Description)
	assert.Equal(t, StateIDOpen, event.Issue.StateID)
	assert.Equal(t, "opened", event.Issue.State)
	assert.Equal(t, "unknown", event.Issue.Severity)
	assert.False(t, event.Issue.Confidential)
}

func TestEmojiEventUnmarshalMergeRequest(t *testing.T) {
	t.Parallel()

	// GIVEN an emoji event JSON payload on a merge request
	jsonObject := loadFixture(t, "testdata/webhooks/emoji_merge_request.json")

	// WHEN the JSON is unmarshaled
	var event *EmojiEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "emoji", event.ObjectKind)
	assert.Equal(t, "award", event.EventType)
	assert.Equal(t, "MergeRequest", event.ObjectAttributes.AwardableType)

	// MergeRequest assertions
	assert.NotNil(t, event.MergeRequest)
	assert.Equal(t, int64(123), event.MergeRequest.ID)
	assert.Equal(t, int64(1), event.MergeRequest.IID)
	assert.Equal(t, "Test Merge Request", event.MergeRequest.Title)
	assert.Equal(t, "opened", event.MergeRequest.State)

	// Issue and Snippet should be nil
	assert.Nil(t, event.Issue)
	assert.Nil(t, event.ProjectSnippet)
	assert.Nil(t, event.Note)
	assert.Nil(t, event.Commit)
}

func TestEmojiEventUnmarshalSnippet(t *testing.T) {
	t.Parallel()

	// GIVEN an emoji event JSON payload on a snippet
	jsonObject := loadFixture(t, "testdata/webhooks/emoji_snippet.json")

	// WHEN the JSON is unmarshaled
	var event *EmojiEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "emoji", event.ObjectKind)
	assert.Equal(t, "award", event.EventType)
	assert.Equal(t, "Snippet", event.ObjectAttributes.AwardableType)

	// Snippet assertions
	assert.NotNil(t, event.ProjectSnippet)
	assert.Equal(t, int64(456), event.ProjectSnippet.ID)
	assert.Equal(t, "Test Snippet", event.ProjectSnippet.Title)
	assert.Equal(t, "ProjectSnippet", event.ProjectSnippet.Type)

	// Issue and MergeRequest should be nil
	assert.Nil(t, event.Issue)
	assert.Nil(t, event.MergeRequest)
	assert.Nil(t, event.Note)
	assert.Nil(t, event.Commit)
}

func TestEmojiEventUnmarshalNoteIssue(t *testing.T) {
	t.Parallel()

	// GIVEN an emoji event JSON payload on a note on an issue
	jsonObject := loadFixture(t, "testdata/webhooks/emoji_note_issue.json")

	// WHEN the JSON is unmarshaled
	var event *EmojiEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "emoji", event.ObjectKind)
	assert.Equal(t, "award", event.EventType)
	assert.Equal(t, "Note", event.ObjectAttributes.AwardableType)

	// Note assertions
	assert.NotNil(t, event.Note)
	assert.Equal(t, int64(789), event.Note.ID)
	assert.Equal(t, "Issue", event.Note.NoteableType)
	assert.Equal(t, int64(123), event.Note.NoteableID)

	// Issue assertions
	assert.NotNil(t, event.Issue)
	assert.Equal(t, int64(123), event.Issue.ID)

	// MergeRequest and Snippet should be nil
	assert.Nil(t, event.MergeRequest)
	assert.Nil(t, event.ProjectSnippet)
	assert.Nil(t, event.Commit)
}

func TestEmojiEventUnmarshalNoteMergeRequest(t *testing.T) {
	t.Parallel()

	// GIVEN an emoji event JSON payload on a note on a merge request
	jsonObject := loadFixture(t, "testdata/webhooks/emoji_note_merge_request.json")

	// WHEN the JSON is unmarshaled
	var event *EmojiEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "emoji", event.ObjectKind)
	assert.Equal(t, "award", event.EventType)
	assert.Equal(t, "Note", event.ObjectAttributes.AwardableType)

	// Note assertions
	assert.NotNil(t, event.Note)
	assert.Equal(t, int64(790), event.Note.ID)
	assert.Equal(t, "MergeRequest", event.Note.NoteableType)

	// MergeRequest assertions
	assert.NotNil(t, event.MergeRequest)
	assert.Equal(t, int64(123), event.MergeRequest.ID)

	// Issue and Snippet should be nil
	assert.Nil(t, event.Issue)
	assert.Nil(t, event.ProjectSnippet)
	assert.Nil(t, event.Commit)
}

func TestEmojiEventUnmarshalNoteSnippet(t *testing.T) {
	t.Parallel()

	// GIVEN an emoji event JSON payload on a note on a snippet
	jsonObject := loadFixture(t, "testdata/webhooks/emoji_note_snippet.json")

	// WHEN the JSON is unmarshaled
	var event *EmojiEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "emoji", event.ObjectKind)
	assert.Equal(t, "award", event.EventType)
	assert.Equal(t, "Note", event.ObjectAttributes.AwardableType)

	// Note assertions
	assert.NotNil(t, event.Note)
	assert.Equal(t, int64(791), event.Note.ID)
	assert.Equal(t, "Snippet", event.Note.NoteableType)

	// Snippet assertions
	assert.NotNil(t, event.ProjectSnippet)
	assert.Equal(t, int64(456), event.ProjectSnippet.ID)

	// Issue and MergeRequest should be nil
	assert.Nil(t, event.Issue)
	assert.Nil(t, event.MergeRequest)
	assert.Nil(t, event.Commit)
}

func TestEmojiEventUnmarshalNoteCommit(t *testing.T) {
	t.Parallel()

	// GIVEN an emoji event JSON payload on a note on a commit
	jsonObject := loadFixture(t, "testdata/webhooks/emoji_note_commit.json")

	// WHEN the JSON is unmarshaled
	var event *EmojiEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "emoji", event.ObjectKind)
	assert.Equal(t, "award", event.EventType)
	assert.Equal(t, "Note", event.ObjectAttributes.AwardableType)

	// Note assertions
	assert.NotNil(t, event.Note)
	assert.Equal(t, int64(792), event.Note.ID)
	assert.Equal(t, "Commit", event.Note.NoteableType)
	assert.NotNil(t, event.Note.CommitID)
	assert.Equal(t, "cfe32cf61b73a0d5e9f13e774abde7ff789b1660", *event.Note.CommitID)

	// Commit assertions
	assert.NotNil(t, event.Commit)
	assert.Equal(t, "cfe32cf61b73a0d5e9f13e774abde7ff789b1660", event.Commit.ID)
	assert.Equal(t, "Add submodule", event.Commit.Title)

	// Issue, MergeRequest and Snippet should be nil
	assert.Nil(t, event.Issue)
	assert.Nil(t, event.MergeRequest)
	assert.Nil(t, event.ProjectSnippet)
}

func TestMilestoneWebhookEventUnmarshal(t *testing.T) {
	t.Parallel()

	// GIVEN a milestone webhook event JSON payload
	jsonObject := loadFixture(t, "testdata/webhooks/milestone_project.json")

	// WHEN the JSON is unmarshaled
	var event *MilestoneWebhookEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "milestone", event.ObjectKind)
	assert.Equal(t, "milestone", event.EventType)
	assert.Equal(t, "create", event.Action)

	// Project assertions (project milestone)
	assert.NotNil(t, event.Project)
	assert.Equal(t, int64(7), event.Project.ID)
	assert.Equal(t, "Flight", event.Project.Name)
	assert.Equal(t, "flightjs/Flight", event.Project.PathWithNamespace)
	assert.Equal(t, int64(0), event.Project.VisibilityLevel)

	// Group should be nil for project milestone
	assert.Nil(t, event.Group)

	// Object attributes assertions
	assert.Equal(t, int64(42), event.ObjectAttributes.ID)
	assert.Equal(t, int64(1), event.ObjectAttributes.IID)
	assert.Equal(t, "v1.0.0", event.ObjectAttributes.Title)
	assert.Equal(t, "First major release milestone", event.ObjectAttributes.Description)
	assert.Equal(t, "active", event.ObjectAttributes.State)
	assert.Equal(t, int64(7), event.ObjectAttributes.ProjectID)
	assert.Nil(t, event.ObjectAttributes.GroupID)

	// Date assertions
	dueDate, err := ParseISOTime("2024-03-01")
	assert.NoError(t, err)
	assert.Equal(t, &dueDate, event.ObjectAttributes.DueDate)

	startDate, err := ParseISOTime("2024-01-01")
	assert.NoError(t, err)
	assert.Equal(t, &startDate, event.ObjectAttributes.StartDate)
}

func TestMilestoneWebhookEventUnmarshalGroup(t *testing.T) {
	t.Parallel()

	// GIVEN a group milestone webhook event JSON payload
	jsonObject := loadFixture(t, "testdata/webhooks/milestone_group.json")

	// WHEN the JSON is unmarshaled
	var event *MilestoneWebhookEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "milestone", event.ObjectKind)
	assert.Equal(t, "milestone", event.EventType)
	assert.Equal(t, "create", event.Action)

	// Project should be empty for group milestone
	assert.Empty(t, event.Project)

	// Group assertions (group milestone)
	assert.NotNil(t, event.Group)
	assert.Equal(t, int64(35), event.Group.GroupID)
	assert.Equal(t, "Flightjs", event.Group.GroupName)
	assert.Equal(t, "flightjs", event.Group.GroupPath)
	assert.Equal(t, "flightjs", event.Group.FullPath)

	// Object attributes assertions
	assert.Equal(t, int64(42), event.ObjectAttributes.ID)
	assert.Equal(t, int64(1), event.ObjectAttributes.IID)
	assert.Equal(t, "v1.0.0", event.ObjectAttributes.Title)
	assert.Equal(t, int64(35), *event.ObjectAttributes.GroupID)
	assert.Equal(t, int64(0), event.ObjectAttributes.ProjectID)

	// Date assertions
	dueDate, err := ParseISOTime("2024-03-01")
	assert.NoError(t, err)
	assert.Equal(t, &dueDate, event.ObjectAttributes.DueDate)

	startDate, err := ParseISOTime("2024-01-01")
	assert.NoError(t, err)
	assert.Equal(t, &startDate, event.ObjectAttributes.StartDate)
}

func TestProjectWebhookEventUnmarshal(t *testing.T) {
	t.Parallel()

	// GIVEN a project webhook event JSON payload
	jsonObject := loadFixture(t, "testdata/webhooks/project.json")

	// WHEN the JSON is unmarshaled
	var event *ProjectWebhookEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "project_create", event.EventName)
	assert.Equal(t, "2024-01-24 16:27:40 UTC", event.CreatedAt)
	assert.Equal(t, "2024-01-24 16:27:40 UTC", event.UpdatedAt)
	assert.Equal(t, "Flight", event.Name)
	assert.Equal(t, "flight", event.Path)
	assert.Equal(t, "flightjs/flight", event.PathWithNamespace)
	assert.Equal(t, int64(7), event.ProjectID)
	assert.Equal(t, int64(35), event.ProjectNamespaceID)
	assert.Equal(t, "private", event.ProjectVisibility)
	assert.Empty(t, event.OldPathWithNamespace)

	// Owners assertions
	assert.Len(t, event.Owners, 1)
	assert.Equal(t, "Administrator", event.Owners[0].Name)
	assert.Equal(t, "admin@example.com", event.Owners[0].Email)
}

func TestVulnerabilityEventUnmarshal(t *testing.T) {
	t.Parallel()

	// GIVEN a vulnerability event JSON payload
	jsonObject := loadFixture(t, "testdata/webhooks/vulnerability.json")

	// WHEN the JSON is unmarshaled
	var event *VulnerabilityEvent
	err := json.Unmarshal(jsonObject, &event)

	// THEN no error should occur and the event should be populated correctly
	assert.NoError(t, err)
	assert.NotNil(t, event)

	assert.Equal(t, "vulnerability", event.ObjectKind)

	// Object attributes assertions
	assert.Equal(t, int64(42), event.ObjectAttributes.ID)
	assert.Equal(t, "https://example.com/flightjs/Flight/-/security/vulnerabilities/42", event.ObjectAttributes.URL)
	assert.Equal(t, "Potential SQL Injection", event.ObjectAttributes.Title)
	assert.Equal(t, "detected", event.ObjectAttributes.State)
	assert.Equal(t, int64(7), event.ObjectAttributes.ProjectID)
	assert.Equal(t, "high", event.ObjectAttributes.Severity)
	assert.False(t, event.ObjectAttributes.SeverityOverridden)
	assert.Equal(t, "sast", event.ObjectAttributes.ReportType)
	assert.Equal(t, "high", event.ObjectAttributes.Confidence)
	assert.False(t, event.ObjectAttributes.ConfidenceOverridden)
	assert.Equal(t, int64(1), event.ObjectAttributes.ConfirmedByID)
	assert.False(t, event.ObjectAttributes.AutoResolved)
	assert.False(t, event.ObjectAttributes.ResolvedOnDefaultBranch)

	// Location assertions
	assert.Equal(t, "app/models/user.rb", event.ObjectAttributes.Location.File)
	assert.Equal(t, "pg", event.ObjectAttributes.Location.Dependency.Package.Name)
	assert.Equal(t, "1.2.3", event.ObjectAttributes.Location.Dependency.Version)

	// CVSS assertions
	assert.Len(t, event.ObjectAttributes.CVSS, 1)
	assert.Equal(t, "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", event.ObjectAttributes.CVSS[0].Vector)
	assert.Equal(t, "GitLab", event.ObjectAttributes.CVSS[0].Vendor)

	// Identifiers assertions
	assert.Len(t, event.ObjectAttributes.Identifiers, 1)
	assert.Equal(t, "CVE-2024-1234", event.ObjectAttributes.Identifiers[0].Name)
	assert.Equal(t, "CVE-2024-1234", event.ObjectAttributes.Identifiers[0].ExternalID)
	assert.Equal(t, "cve", event.ObjectAttributes.Identifiers[0].ExternalType)

	// Issues assertions
	assert.Len(t, event.ObjectAttributes.Issues, 1)
	assert.Equal(t, "Fix SQL Injection vulnerability", event.ObjectAttributes.Issues[0].Title)
	assert.Equal(t, "https://example.com/flightjs/Flight/-/issues/10", event.ObjectAttributes.Issues[0].URL)
}
