Skip to content

Commit

Permalink
DEVTOOLING-966: Validating that the schedule start date matches the r…
Browse files Browse the repository at this point in the history
…rule (#1430)

* Verifying schedule start date is a weekday

* make docs

* Verifying that schedule start date matches rrule

* Moving map outside function
  • Loading branch information
charliecon authored Dec 23, 2024
1 parent a17d2bf commit 17a077d
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 71 deletions.
2 changes: 1 addition & 1 deletion docs/resources/architect_schedules.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ resource "genesyscloud_architect_schedules" "sample_schedule" {

- `end` (String) Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000.
- `name` (String) Name of the schedule.
- `start` (String) Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000.
- `start` (String) Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000. The start date should be applicable to the schedule's recurrence rule.

### Optional

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ func TestAccDataSourceArchitectSchedule(t *testing.T) {
start = "2021-08-04T08:00:00.000000"
end = "2021-08-04T17:00:00.000000"
rrule = "FREQ=DAILY;INTERVAL=1"

resourcePath = ResourceType + "." + schedResourceLabel
dataResourcePath = "data." + ResourceType + "." + schedDataLabel
)

resource.Test(t, resource.TestCase{
Expand All @@ -37,24 +40,22 @@ func TestAccDataSourceArchitectSchedule(t *testing.T) {
) + generateScheduleDataSource(
schedDataLabel,
name,
"genesyscloud_architect_schedules."+schedResourceLabel),
resourcePath),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair("data.genesyscloud_architect_schedules."+schedDataLabel, "id", "genesyscloud_architect_schedules."+schedResourceLabel, "id"),
resource.TestCheckResourceAttrPair(dataResourcePath, "id", resourcePath, "id"),
),
},
},
})
}

func generateScheduleDataSource(
resourceLabel string,
name string,
// Must explicitly use depends_on in terraform v0.13 when a data source references a resource
// Fixed in v0.14 https://github.com/hashicorp/terraform/pull/26284
resourceLabel,
name,
dependsOnResource string) string {
return fmt.Sprintf(`data "genesyscloud_architect_schedules" "%s" {
return fmt.Sprintf(`data "%s" "%s" {
name = "%s"
depends_on=[%s]
}
`, resourceLabel, name, dependsOnResource)
`, ResourceType, resourceLabel, name, dependsOnResource)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (
"github.com/mypurecloud/platform-client-sdk-go/v149/platformclientv2"
)

const timeFormat = "2006-01-02T15:04:05.000000"

func getAllArchitectSchedules(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getArchitectSchedulesProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
Expand Down Expand Up @@ -51,14 +49,20 @@ func createArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta
description := d.Get("description").(string)
start := d.Get("start").(string)
end := d.Get("end").(string)
rrule := d.Get("rrule").(string)
rrule, _ := d.Get("rrule").(string)

//The first parameter of the Parse() method specifies the date and time format/layout that should be used to interpret the second parameter.
schedStart, err := time.Parse(timeFormat, start)
if err != nil {
return util.BuildDiagnosticError(ResourceType, fmt.Sprintf("Failed to parse date %s", start), err)
}

if rrule != "" {
if err := verifyStartDateConformsToRRule(schedStart, rrule, name); err != nil {
return util.BuildDiagnosticError(ResourceType, err.Error(), err)
}
}

schedEnd, err := time.Parse(timeFormat, end)
if err != nil {
return util.BuildDiagnosticError(ResourceType, fmt.Sprintf("Failed to parse date %s", end), err)
Expand Down Expand Up @@ -116,15 +120,13 @@ func readArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta in
start := new(string)
if scheduleResponse.Start != nil {
*start = timeutil.Strftime(scheduleResponse.Start, "%Y-%m-%dT%H:%M:%S.%f")

} else {
start = nil
}

end := new(string)
if scheduleResponse.End != nil {
*end = timeutil.Strftime(scheduleResponse.End, "%Y-%m-%dT%H:%M:%S.%f")

} else {
end = nil
}
Expand All @@ -151,14 +153,20 @@ func updateArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta
description := d.Get("description").(string)
start := d.Get("start").(string)
end := d.Get("end").(string)
rrule := d.Get("rrule").(string)
rrule, _ := d.Get("rrule").(string)

//The first parameter of the Parse() method specifies the date and time format/layout that should be used to interpret the second parameter.
schedStart, err := time.Parse(timeFormat, start)
if err != nil {
return diag.Errorf("Failed to parse date %s: %s", start, err)
}

if rrule != "" {
if err := verifyStartDateConformsToRRule(schedStart, rrule, name); err != nil {
return util.BuildDiagnosticError(ResourceType, err.Error(), err)
}
}

schedEnd, err := time.Parse(timeFormat, end)
if err != nil {
return diag.Errorf("Failed to parse date %s: %s", end, err)
Expand Down Expand Up @@ -239,22 +247,3 @@ func deleteArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceType, fmt.Sprintf("Schedule %s still exists", d.Id()), proxyGetResponse))
})
}

func GenerateArchitectSchedulesResource(
schedResourceLabel string,
name string,
divisionId string,
description string,
start string,
end string,
rrule string) string {
return fmt.Sprintf(`resource "genesyscloud_architect_schedules" "%s" {
name = "%s"
division_id = %s
description = "%s"
start = "%s"
end = "%s"
rrule = "%s"
}
`, schedResourceLabel, name, divisionId, description, start, end, rrule)
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func ResourceArchitectSchedules() *schema.Resource {
Optional: true,
},
"start": {
Description: "Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000.",
Description: "Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000. The start date should be applicable to the schedule's recurrence rule.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validators.ValidateLocalDateTimes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package architect_schedules

import (
"fmt"
"regexp"
authDivision "terraform-provider-genesyscloud/genesyscloud/auth_division"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
Expand All @@ -13,6 +14,8 @@ import (
"github.com/mypurecloud/platform-client-sdk-go/v149/platformclientv2"
)

const errorMessageToMatch = "invalid start date."

func TestAccResourceArchitectSchedules(t *testing.T) {
var (
schedResourceLabel1 = "arch-sched1"
Expand All @@ -28,6 +31,9 @@ func TestAccResourceArchitectSchedules(t *testing.T) {

divResourceLabel = "test-division"
divName = "terraform-" + uuid.NewString()

resourcePath1 = ResourceType + "." + schedResourceLabel1
resourcePath2 = ResourceType + "." + schedResourceLabel2
)

resource.Test(t, resource.TestCase{
Expand All @@ -46,12 +52,12 @@ func TestAccResourceArchitectSchedules(t *testing.T) {
rrule,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "name", name),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "description", description),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "start", start),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "end", end),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "rrule", rrule),
provider.TestDefaultHomeDivision("genesyscloud_architect_schedules."+schedResourceLabel1),
resource.TestCheckResourceAttr(resourcePath1, "name", name),
resource.TestCheckResourceAttr(resourcePath1, "description", description),
resource.TestCheckResourceAttr(resourcePath1, "start", start),
resource.TestCheckResourceAttr(resourcePath1, "end", end),
resource.TestCheckResourceAttr(resourcePath1, "rrule", rrule),
provider.TestDefaultHomeDivision(resourcePath1),
),
},
{
Expand All @@ -66,12 +72,12 @@ func TestAccResourceArchitectSchedules(t *testing.T) {
rrule,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "name", name),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "description", description),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "start", start2),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "end", end),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel1, "rrule", rrule),
provider.TestDefaultHomeDivision("genesyscloud_architect_schedules."+schedResourceLabel1),
resource.TestCheckResourceAttr(resourcePath1, "name", name),
resource.TestCheckResourceAttr(resourcePath1, "description", description),
resource.TestCheckResourceAttr(resourcePath1, "start", start2),
resource.TestCheckResourceAttr(resourcePath1, "end", end),
resource.TestCheckResourceAttr(resourcePath1, "rrule", rrule),
provider.TestDefaultHomeDivision(resourcePath1),
),
},
{
Expand All @@ -86,17 +92,17 @@ func TestAccResourceArchitectSchedules(t *testing.T) {
rrule,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel2, "name", name2),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel2, "description", description),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel2, "start", start),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel2, "end", end),
resource.TestCheckResourceAttr("genesyscloud_architect_schedules."+schedResourceLabel2, "rrule", rrule),
resource.TestCheckResourceAttrPair("genesyscloud_architect_schedules."+schedResourceLabel2, "division_id", "genesyscloud_auth_division."+divResourceLabel, "id"),
resource.TestCheckResourceAttr(resourcePath2, "name", name2),
resource.TestCheckResourceAttr(resourcePath2, "description", description),
resource.TestCheckResourceAttr(resourcePath2, "start", start),
resource.TestCheckResourceAttr(resourcePath2, "end", end),
resource.TestCheckResourceAttr(resourcePath2, "rrule", rrule),
resource.TestCheckResourceAttrPair(resourcePath2, "division_id", "genesyscloud_auth_division."+divResourceLabel, "id"),
),
},
{
// Import/Read
ResourceName: "genesyscloud_architect_schedules." + schedResourceLabel2,
ResourceName: resourcePath2,
ImportState: true,
ImportStateVerify: true,
},
Expand All @@ -105,23 +111,112 @@ func TestAccResourceArchitectSchedules(t *testing.T) {
})
}

func TestAccResourceArchitectSchedulesCreateFailsWhenStartDateNotInRRule(t *testing.T) {
var (
schedResourceLabel = "schedule"
name = "CX as Code Schedule " + uuid.NewString()
description = "Sample Schedule by CX as Code"
start = "2021-08-07T22:00:00.000000" // Saturday
end = "2021-08-08T23:00:00.000000"
rrule = "FREQ=DAILY;INTERVAL=1;BYDAY=MO,TU,WE,THU,FR,SU"
)

resource.Test(t, resource.TestCase{
PreCheck: func() { util.TestAccPreCheck(t) },
ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources),
Steps: []resource.TestStep{
{
// Create
Config: GenerateArchitectSchedulesResource(
schedResourceLabel,
name,
util.NullValue,
description,
start,
end,
rrule,
),
ExpectError: regexp.MustCompile(errorMessageToMatch),
},
},
CheckDestroy: testVerifySchedulesDestroyed,
})
}

func TestAccResourceArchitectSchedulesUpdateFailsWhenStartDateNotInRRule(t *testing.T) {
var (
schedResourceLabel = "schedule"
name = "CX as Code Schedule " + uuid.NewString()
description = "Sample Schedule by CX as Code"
rrule = "FREQ=DAILY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR"

validStart = "2021-08-06T22:00:00.000000" // Friday
validEnd = "2021-08-06T23:00:00.000000"

startUpdate = "2021-08-08T22:00:00.000000" // Sunday
endUpdate = "2021-08-09T22:00:00.000000"

resourcePath = ResourceType + "." + schedResourceLabel
)

resource.Test(t, resource.TestCase{
PreCheck: func() { util.TestAccPreCheck(t) },
ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources),
Steps: []resource.TestStep{
{
// Create
Config: GenerateArchitectSchedulesResource(
schedResourceLabel,
name,
util.NullValue,
description,
validStart,
validEnd,
rrule,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourcePath, "name", name),
resource.TestCheckResourceAttr(resourcePath, "description", description),
resource.TestCheckResourceAttr(resourcePath, "start", validStart),
resource.TestCheckResourceAttr(resourcePath, "end", validEnd),
resource.TestCheckResourceAttr(resourcePath, "rrule", rrule),
provider.TestDefaultHomeDivision(resourcePath),
),
},
{
// Update
Config: GenerateArchitectSchedulesResource(
schedResourceLabel,
name,
util.NullValue,
description,
startUpdate,
endUpdate,
rrule,
),
ExpectError: regexp.MustCompile(errorMessageToMatch),
},
},
CheckDestroy: testVerifySchedulesDestroyed,
})
}

func testVerifySchedulesDestroyed(state *terraform.State) error {
archAPI := platformclientv2.NewArchitectApi()
for _, rs := range state.RootModule().Resources {
if rs.Type != "genesyscloud_architect_schedules" {
if rs.Type != ResourceType {
continue
}

sched, resp, err := archAPI.GetArchitectSchedule(rs.Primary.ID)
if sched != nil {
return fmt.Errorf("Schedule (%s) still exists", rs.Primary.ID)
} else if util.IsStatus404(resp) {
// Schedule not found as expected
continue
} else {
// Unexpected error
return fmt.Errorf("Unexpected error: %s", err)
_, resp, err := archAPI.GetArchitectSchedule(rs.Primary.ID)
if err != nil {
if util.IsStatus404(resp) {
// Schedule not found as expected
continue
}
return fmt.Errorf("unexpected error: %s", err)
}
return fmt.Errorf("schedule (%s) still exists", rs.Primary.ID)
}
// Success. All schedules destroyed
return nil
Expand Down
Loading

0 comments on commit 17a077d

Please sign in to comment.