diff --git a/.changes/unreleased/Feature-20231115-143536.yaml b/.changes/unreleased/Feature-20231115-143536.yaml new file mode 100644 index 00000000..4785d358 --- /dev/null +++ b/.changes/unreleased/Feature-20231115-143536.yaml @@ -0,0 +1,3 @@ +kind: Feature +body: Add support for Property Definitions +time: 2023-12-07T15:22:26.29648-05:00 diff --git a/property.go b/property.go new file mode 100644 index 00000000..a0de1c9f --- /dev/null +++ b/property.go @@ -0,0 +1,89 @@ +package opslevel + +import "fmt" + +type PropertyDefinitionInput struct { + Name string `json:"name"` + Schema JSONString `json:"schema"` +} + +type PropertyDefinition struct { + Aliases []string `graphql:"aliases" json:"aliases"` + Id ID `graphql:"id" json:"id"` + Name string `graphql:"name" json:"name"` + Schema JSON `json:"schema" scalar:"true"` // Do not add graphql struct tag here +} + +type PropertyDefinitionConnection struct { + Nodes []PropertyDefinition + PageInfo PageInfo + TotalCount int `graphql:"-"` +} + +func (client *Client) CreatePropertyDefinition(input PropertyDefinitionInput) (*PropertyDefinition, error) { + var m struct { + Payload struct { + Definition PropertyDefinition `graphql:"definition"` + Errors []OpsLevelErrors `graphql:"errors"` + } `graphql:"propertyDefinitionCreate(input: $input)"` + } + v := PayloadVariables{ + "input": input, + } + err := client.Mutate(&m, v, WithName("PropertyDefinitionCreate")) + return &m.Payload.Definition, HandleErrors(err, m.Payload.Errors) +} + +func (client *Client) GetPropertyDefinition(input string) (*PropertyDefinition, error) { + var q struct { + Account struct { + Definition PropertyDefinition `graphql:"propertyDefinition(input: $input)"` + } + } + v := PayloadVariables{ + "input": *NewIdentifier(input), + } + err := client.Query(&q, v, WithName("PropertyDefinitionGet")) + if q.Account.Definition.Id == "" { + err = fmt.Errorf("PropertyDefinition with ID or Alias matching '%s' not found", input) + } + return &q.Account.Definition, HandleErrors(err, nil) +} + +func (client *Client) ListPropertyDefinitions(variables *PayloadVariables) (PropertyDefinitionConnection, error) { + var q struct { + Account struct { + Definitions PropertyDefinitionConnection `graphql:"propertyDefinitions(after: $after, first: $first)"` + } + } + if variables == nil { + variables = client.InitialPageVariablesPointer() + } + if err := client.Query(&q, *variables, WithName("PropertyDefinitionList")); err != nil { + return PropertyDefinitionConnection{}, err + } + for q.Account.Definitions.PageInfo.HasNextPage { + (*variables)["after"] = q.Account.Definitions.PageInfo.End + resp, err := client.ListPropertyDefinitions(variables) + if err != nil { + return PropertyDefinitionConnection{}, err + } + q.Account.Definitions.Nodes = append(q.Account.Definitions.Nodes, resp.Nodes...) + q.Account.Definitions.PageInfo = resp.PageInfo + q.Account.Definitions.TotalCount += len(q.Account.Definitions.Nodes) + } + return q.Account.Definitions, nil +} + +func (client *Client) DeletePropertyDefinition(input string) error { + var m struct { + Payload struct { + Errors []OpsLevelErrors `graphql:"errors"` + } `graphql:"propertyDefinitionDelete(resource: $input)"` + } + v := PayloadVariables{ + "input": *NewIdentifier(input), + } + err := client.Mutate(&m, v, WithName("PropertyDefinitionDelete")) + return HandleErrors(err, m.Payload.Errors) +} diff --git a/property_test.go b/property_test.go new file mode 100644 index 00000000..63ba2416 --- /dev/null +++ b/property_test.go @@ -0,0 +1,135 @@ +package opslevel_test + +import ( + "fmt" + "testing" + + ol "github.com/opslevel/opslevel-go/v2023" + "github.com/rocktavious/autopilot/v2023" +) + +const schemaString = `{"$ref":"#/$defs/MyProp","$defs":{"MyProp":{"properties":{"name":{"type":"string","title":"the name","description":"The name of a friend","default":"alex","examples":["joe","lucy"]}},"additionalProperties":false,"type":"object","required":["name"]}}}` + +func TestCreatePropertyDefinition(t *testing.T) { + // Arrange + schema := ol.NewJSON(schemaString) + expectedPropertyDefinition := autopilot.Register[ol.PropertyDefinition]("expected_property_definition", ol.PropertyDefinition{ + Aliases: []string{"my_prop"}, + Id: "XXX", + Name: "my-prop", + Schema: schema, + }) + propertyDefinitionInput := autopilot.Register[ol.PropertyDefinitionInput]("property_definition_input", ol.PropertyDefinitionInput{ + Name: "my-prop", + Schema: ol.JSONString(schemaString), + }) + testRequest := NewTestRequest( + `"mutation PropertyDefinitionCreate($input:PropertyDefinitionInput!){propertyDefinitionCreate(input: $input){definition{aliases,id,name,schema},errors{message,path}}}"`, + `{"input": {{ template "property_definition_input" }} }`, + fmt.Sprintf(`{"data":{"propertyDefinitionCreate":{"definition": {"aliases":["my_prop"],"id":"XXX","name":"my-prop","schema": %s}, "errors":[] }}}`, schema.ToJSON()), + ) + client := BestTestClient(t, "properties/definition_create", testRequest) + + // Act + actualPropertyDefinition, err := client.CreatePropertyDefinition(propertyDefinitionInput) + + // Assert + autopilot.Ok(t, err) + autopilot.Equals(t, expectedPropertyDefinition, *actualPropertyDefinition) + autopilot.Equals(t, ol.JSON(propertyDefinitionInput.Schema.Map()), actualPropertyDefinition.Schema) +} + +func TestDeletePropertyDefinition(t *testing.T) { + // Arrange + testRequest := NewTestRequest( + `"mutation PropertyDefinitionDelete($input:IdentifierInput!){propertyDefinitionDelete(resource: $input){errors{message,path}}}"`, + `{"input":{"alias":"my_prop"}}`, + `{"data":{"propertyDefinitionDelete":{"errors":[]}}}`, + ) + client := BestTestClient(t, "properties/definition_delete", testRequest) + + // Act + err := client.DeletePropertyDefinition("my_prop") + + // Assert + autopilot.Ok(t, err) +} + +func TestGetPropertyDefinition(t *testing.T) { + // Arrange + schema := ol.NewJSON(schemaString) + expectedPropertyDefinition := autopilot.Register[ol.PropertyDefinition]("expected_property_definition", + ol.PropertyDefinition{ + Aliases: []string{"my_prop"}, + Id: "XXX", + Name: "my-prop", + Schema: schema, + }) + testRequest := NewTestRequest( + `"query PropertyDefinitionGet($input:IdentifierInput!){account{propertyDefinition(input: $input){aliases,id,name,schema}}}"`, + `{"input":{"alias":"my_prop"}}`, + fmt.Sprintf(`{"data":{"account":{"propertyDefinition": {"aliases":["my_prop"],"id":"XXX","name":"my-prop","schema": %s }}}}`, schema.ToJSON()), + ) + client := BestTestClient(t, "properties/definition_get", testRequest) + + // Act + property, err := client.GetPropertyDefinition("my_prop") + + // Assert + autopilot.Ok(t, err) + autopilot.Equals(t, "XXX", string(property.Id)) + autopilot.Equals(t, expectedPropertyDefinition, *property) + autopilot.Equals(t, "my-prop", property.Name) + autopilot.Equals(t, schema, property.Schema) +} + +func TestListPropertyDefinitions(t *testing.T) { + // Arrange + schema := ol.NewJSON(schemaString) + expectedPropDefsPageOne := autopilot.Register[[]ol.PropertyDefinition]("property_definitions", []ol.PropertyDefinition{ + { + Aliases: []string{"prop1"}, + Id: "XXX", + Name: "prop1", + Schema: ol.NewJSON(schemaString), + }, + { + Aliases: []string{"prop2"}, + Id: "XXX", + Name: "prop2", + Schema: ol.NewJSON(schemaString), + }, + }) + expectedPropDefPageTwo := autopilot.Register[ol.PropertyDefinition]("property_definition_3", ol.PropertyDefinition{ + Aliases: []string{"prop3"}, + Id: "XXX", + Name: "prop3", + Schema: ol.NewJSON(schemaString), + }) + testRequestOne := NewTestRequest( + `"query PropertyDefinitionList($after:String!$first:Int!){account{propertyDefinitions(after: $after, first: $first){nodes{aliases,id,name,schema},{{ template "pagination_request" }}}}}"`, + `{{ template "pagination_initial_query_variables" }}`, + fmt.Sprintf(`{"data":{"account":{"propertyDefinitions":{"nodes":[{"aliases":["prop1"],"id":"XXX","name":"prop1","schema": %s},{"aliases":["prop2"],"id":"XXX","name":"prop2","schema": %s}],{{ template "pagination_initial_pageInfo_response" }}}}}}`, schema.ToJSON(), schema.ToJSON()), + ) + testRequestTwo := NewTestRequest( + `"query PropertyDefinitionList($after:String!$first:Int!){account{propertyDefinitions(after: $after, first: $first){nodes{aliases,id,name,schema},{{ template "pagination_request" }}}}}"`, + `{{ template "pagination_second_query_variables" }}`, + fmt.Sprintf(`{"data":{"account":{"propertyDefinitions":{"nodes":[{"aliases":["prop3"],"id":"XXX","name":"prop3","schema": %s}],{{ template "pagination_second_pageInfo_response" }}}}}}`, schema.ToJSON()), + ) + requests := []TestRequest{testRequestOne, testRequestTwo} + client := BestTestClient(t, "properties/definition_list", requests...) + + // Act + properties, err := client.ListPropertyDefinitions(nil) + result := properties.Nodes + + // Assert + autopilot.Ok(t, err) + autopilot.Equals(t, 3, len(result)) + autopilot.Equals(t, expectedPropDefsPageOne[0], result[0]) + autopilot.Equals(t, expectedPropDefsPageOne[1], result[1]) + autopilot.Equals(t, expectedPropDefPageTwo, result[2]) + autopilot.Equals(t, expectedPropDefsPageOne[0].Schema, result[0].Schema) + autopilot.Equals(t, expectedPropDefsPageOne[1].Schema, result[1].Schema) + autopilot.Equals(t, expectedPropDefPageTwo.Schema, result[2].Schema) +}