Skip to content
Open
136 changes: 136 additions & 0 deletions campaign.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package opslevel

import (
"fmt"

"github.com/hasura/go-graphql-client"
)

type ListCampaignsVariables struct {
After *string
First *int
Expand All @@ -25,6 +31,136 @@ func (v *ListCampaignsVariables) AsPayloadVariables() *PayloadVariables {
return &variables
}

func (client *Client) CreateCampaign(input CampaignCreateInput) (*Campaign, error) {
var m struct {
Payload CampaignCreatePayload `graphql:"campaignCreate(input: $input)"`
}
v := PayloadVariables{
"input": input,
}
err := client.Mutate(&m, v, WithName("CampaignCreate"))
return &m.Payload.Campaign, HandleErrors(err, m.Payload.Errors)
}

func (client *Client) GetCampaign(id ID) (*Campaign, error) {
var q struct {
Account struct {
Campaign Campaign `graphql:"campaign(id: $id)"`
}
}
v := PayloadVariables{
"id": id,
}
err := client.Query(&q, v, WithName("CampaignGet"))
if q.Account.Campaign.Id == "" {
err = graphql.Errors{graphql.Error{
Message: fmt.Sprintf("campaign with ID '%s' not found", id),
Path: []any{"account", "campaign"},
}}
}
return &q.Account.Campaign, HandleErrors(err, nil)
}

func (client *Client) UpdateCampaign(input CampaignUpdateInput) (*Campaign, error) {
var m struct {
Payload CampaignUpdatePayload `graphql:"campaignUpdate(input: $input)"`
}
v := PayloadVariables{
"input": input,
}
err := client.Mutate(&m, v, WithName("CampaignUpdate"))
return &m.Payload.Campaign, HandleErrors(err, m.Payload.Errors)
}

func (client *Client) DeleteCampaign(id ID) error {
var m struct {
Payload CampaignDeletePayload `graphql:"campaignDelete(input: $input)"`
}
v := PayloadVariables{
"input": DeleteInput{Id: id},
}
err := client.Mutate(&m, v, WithName("CampaignDelete"))
return HandleErrors(err, m.Payload.Errors)
}

func (client *Client) ScheduleCampaign(input CampaignScheduleUpdateInput) (*Campaign, error) {
var m struct {
Payload CampaignUpdatePayload `graphql:"campaignScheduleUpdate(input: $input)"`
}
v := PayloadVariables{
"input": input,
}
err := client.Mutate(&m, v, WithName("CampaignScheduleUpdate"))
return &m.Payload.Campaign, HandleErrors(err, m.Payload.Errors)
}

func (client *Client) UnscheduleCampaign(id ID) (*Campaign, error) {
var m struct {
Payload CampaignUnschedulePayload `graphql:"campaignUnschedule(input: $input)"`
}
v := PayloadVariables{
"input": DeleteInput{Id: id},
}
err := client.Mutate(&m, v, WithName("CampaignUnschedule"))
return &m.Payload.Campaign, HandleErrors(err, m.Payload.Errors)
}

// CampaignCheckNode is a lightweight representation of a check belonging to a campaign,
// used when listing campaign checks without needing the full Check interface fragments.
type CampaignCheckNode struct {
Id ID `graphql:"id"`
Name string `graphql:"name"`
}

type campaignCheckConnection struct {
Nodes []CampaignCheckNode `graphql:"nodes"`
PageInfo PageInfo `graphql:"pageInfo"`
}

func (client *Client) ListCampaignChecks(campaignId ID, variables ...*PayloadVariables) ([]CampaignCheckNode, error) {
var q struct {
Account struct {
Campaign struct {
Checks campaignCheckConnection `graphql:"checks(first: $first, after: $after)"`
} `graphql:"campaign(id: $id)"`
}
}

var pages *PayloadVariables
if len(variables) > 0 && variables[0] != nil {
pages = variables[0]
} else {
pages = client.InitialPageVariablesPointer()
(*pages)["id"] = campaignId
}

if err := client.Query(&q, *pages, WithName("CampaignChecksList")); err != nil {
return nil, err
}

allChecks := q.Account.Campaign.Checks.Nodes
if q.Account.Campaign.Checks.PageInfo.HasNextPage {
(*pages)["after"] = q.Account.Campaign.Checks.PageInfo.End
resp, err := client.ListCampaignChecks(campaignId, pages)
if err != nil {
return nil, err
}
allChecks = append(allChecks, resp...)
}
return allChecks, nil
}

func (client *Client) CopyChecksToCampaign(input ChecksCopyToCampaignInput) (*Campaign, error) {
var m struct {
Payload ChecksCopyToCampaignPayload `graphql:"checksCopyToCampaign(input: $input)"`
}
v := PayloadVariables{
"input": input,
}
err := client.Mutate(&m, v, WithName("ChecksCopyToCampaign"))
return &m.Payload.Campaign, HandleErrors(err, m.Payload.Errors)
}

func (client *Client) ListCampaigns(campaignVariables *ListCampaignsVariables) (*CampaignConnection, error) {
if campaignVariables == nil {
campaignVariables = &ListCampaignsVariables{}
Expand Down
196 changes: 194 additions & 2 deletions campaign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,203 @@ import (
"testing"

ol "github.com/opslevel/opslevel-go/v2026"
"github.com/relvacode/iso8601"
"github.com/rocktavious/autopilot/v2023"
)

func TestCreateCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_create_request" }}`,
`{{ template "campaign_create_request_vars" }}`,
`{{ template "campaign_create_response" }}`,
)
client := BestTestClient(t, "campaign/create", testRequest)

brief := "A test campaign"
// Act
campaign, err := client.CreateCampaign(ol.CampaignCreateInput{
Name: "New Campaign",
OwnerId: id1,
FilterId: ol.RefOf(id2),
ProjectBrief: &brief,
})

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, "New Campaign", campaign.Name)
autopilot.Equals(t, ol.CampaignStatusEnumDraft, campaign.Status)
autopilot.Equals(t, id1, campaign.Owner.Id)
autopilot.Equals(t, id2, campaign.Filter.Id)
autopilot.Equals(t, "A test campaign", campaign.RawProjectBrief)
}

func TestGetCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_get_request" }}`,
`{{ template "campaign_get_request_vars" }}`,
`{{ template "campaign_get_response" }}`,
)
client := BestTestClient(t, "campaign/get", testRequest)

// Act
campaign, err := client.GetCampaign(id1)

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, campaign.Id)
autopilot.Equals(t, "Fetched Campaign", campaign.Name)
autopilot.Equals(t, ol.CampaignStatusEnumScheduled, campaign.Status)
autopilot.Equals(t, "2026-05-01 00:00:00 +0000 UTC", campaign.StartDate.String())
autopilot.Equals(t, "2026-06-30 00:00:00 +0000 UTC", campaign.TargetDate.String())
}

func TestUpdateCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_update_request" }}`,
`{{ template "campaign_update_request_vars" }}`,
`{{ template "campaign_update_response" }}`,
)
client := BestTestClient(t, "campaign/update", testRequest)

name := "Updated Campaign"
// Act
campaign, err := client.UpdateCampaign(ol.CampaignUpdateInput{
Id: id1,
Name: &name,
OwnerId: ol.RefOf(id2),
})

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, campaign.Id)
autopilot.Equals(t, "Updated Campaign", campaign.Name)
autopilot.Equals(t, id2, campaign.Owner.Id)
}

func TestDeleteCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_delete_request" }}`,
`{{ template "campaign_delete_request_vars" }}`,
`{{ template "campaign_delete_response" }}`,
)
client := BestTestClient(t, "campaign/delete", testRequest)

// Act
err := client.DeleteCampaign(id1)

// Assert
autopilot.Ok(t, err)
}

func TestScheduleCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_schedule_request" }}`,
`{{ template "campaign_schedule_request_vars" }}`,
`{{ template "campaign_schedule_response" }}`,
)
client := BestTestClient(t, "campaign/schedule", testRequest)

startDate, _ := iso8601.ParseString("2026-05-01T00:00:00Z")
targetDate, _ := iso8601.ParseString("2026-06-30T00:00:00Z")

// Act
campaign, err := client.ScheduleCampaign(ol.CampaignScheduleUpdateInput{
Id: id1,
StartDate: iso8601.Time{Time: startDate},
TargetDate: iso8601.Time{Time: targetDate},
})

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, campaign.Id)
autopilot.Equals(t, ol.CampaignStatusEnumScheduled, campaign.Status)
autopilot.Equals(t, "2026-05-01 00:00:00 +0000 UTC", campaign.StartDate.String())
autopilot.Equals(t, "2026-06-30 00:00:00 +0000 UTC", campaign.TargetDate.String())
}

func TestUnscheduleCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_unschedule_request" }}`,
`{{ template "campaign_unschedule_request_vars" }}`,
`{{ template "campaign_unschedule_response" }}`,
)
client := BestTestClient(t, "campaign/unschedule", testRequest)

// Act
campaign, err := client.UnscheduleCampaign(id1)

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, campaign.Id)
autopilot.Equals(t, ol.CampaignStatusEnumDraft, campaign.Status)
autopilot.Equals(t, true, campaign.StartDate.IsZero())
}

func TestCopyChecksToCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_copy_checks_request" }}`,
`{{ template "campaign_copy_checks_request_vars" }}`,
`{{ template "campaign_copy_checks_response" }}`,
)
client := BestTestClient(t, "campaign/copy_checks", testRequest)

// Act
campaign, err := client.CopyChecksToCampaign(ol.ChecksCopyToCampaignInput{
CampaignId: id1,
CheckIds: []ol.ID{id2, id3},
})

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, campaign.Id)
autopilot.Equals(t, 2, campaign.CheckStats.Total)
}

func TestListCampaignChecks(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_list_checks_request" }}`,
`{{ template "campaign_list_checks_request_vars" }}`,
`{{ template "campaign_list_checks_response" }}`,
)
client := BestTestClient(t, "campaign/list_checks", testRequest)

// Act
checks, err := client.ListCampaignChecks(id1)

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, 2, len(checks))
autopilot.Equals(t, id2, checks[0].Id)
autopilot.Equals(t, "Secret Rotation", checks[0].Name)
autopilot.Equals(t, id3, checks[1].Id)
autopilot.Equals(t, "Dependency Scanning", checks[1].Name)
}

func TestListCampaignChecksEmpty(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_list_checks_request" }}`,
`{{ template "campaign_list_checks_request_vars" }}`,
`{{ template "campaign_list_checks_empty_response" }}`,
)
client := BestTestClient(t, "campaign/list_checks_empty", testRequest)

// Act
checks, err := client.ListCampaignChecks(id1)

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, 0, len(checks))
}

func TestListCampaigns(t *testing.T) {
// Arrange
testRequestOne := autopilot.NewTestRequest(
Expand Down Expand Up @@ -40,7 +234,6 @@ func TestListCampaigns(t *testing.T) {
autopilot.Equals(t, "2024-01-01 00:00:00 +0000 UTC", result[0].StartDate.String())
}

// TestListCampaignsVariables_AsPayloadVariables verifies that ListCampaignsVariables produces the correct payload map.
func TestListCampaignsVariables_AsPayloadVariables(t *testing.T) {
after := "cursor"
first := 5
Expand All @@ -61,7 +254,6 @@ func TestListCampaignsVariables_AsPayloadVariables(t *testing.T) {
autopilot.Equals(t, expected, *variables)
}

// TestListCampaignsWithCustomVariables verifies that custom ListCampaignsVariables values are sent in the GraphQL request.
func TestListCampaignsWithCustomVariables(t *testing.T) {
after := "cursor"
first := 5
Expand Down
Loading