Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package acceptancetests

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
Expand Down Expand Up @@ -94,6 +95,318 @@ func TestAccWorkitemtrackingprocessWorkItemType_CreateAndUpdate(t *testing.T) {
})
}

func TestAccWorkitemtrackingprocessWorkItemType_States(t *testing.T) {
workItemTypeName := testutils.GenerateWorkItemTypeName()
processName := testutils.GenerateResourceName()
tfNode := "azuredevops_workitemtrackingprocess_workitemtype.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testutils.PreCheck(t, nil) },
ProviderFactories: testutils.GetProviderFactories(),
CheckDestroy: testutils.CheckProcessDestroyed,
Steps: []resource.TestStep{
{
Config: workItemTypeWithStates(workItemTypeName, processName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(tfNode, "state.#", "2"),
resource.TestCheckResourceAttr(tfNode, "state.0.name", "New Active"),
resource.TestCheckResourceAttr(tfNode, "state.0.order", "1"),
resource.TestCheckResourceAttr(tfNode, "state.1.name", "New Closed"),
resource.TestCheckResourceAttr(tfNode, "state.1.order", "2"),
),
},
{
ResourceName: tfNode,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: getWorkItemTypeStateIdFunc(tfNode),
},
{
Config: workItemTypeWithStatesUpdated(workItemTypeName, processName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(tfNode, "state.#", "4"),
resource.TestCheckResourceAttr(tfNode, "state.0.name", "New"),
resource.TestCheckResourceAttr(tfNode, "state.0.order", "1"),
resource.TestCheckResourceAttr(tfNode, "state.1.name", "New Active"),
resource.TestCheckResourceAttr(tfNode, "state.1.order", "2"),
resource.TestCheckResourceAttr(tfNode, "state.2.name", "Active Order"),
resource.TestCheckResourceAttr(tfNode, "state.2.order", "3"),
resource.TestCheckResourceAttr(tfNode, "state.3.name", "New Closed"),
resource.TestCheckResourceAttr(tfNode, "state.3.order", "4"),
),
},
{
ResourceName: tfNode,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: getWorkItemTypeStateIdFunc(tfNode),
},
{
Config: workItemTypeWithStatesChangingOrder(workItemTypeName, processName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(tfNode, "state.#", "4"),
resource.TestCheckResourceAttr(tfNode, "state.0.name", "New"),
resource.TestCheckResourceAttr(tfNode, "state.0.order", "1"),
resource.TestCheckResourceAttr(tfNode, "state.1.name", "Active Order"),
resource.TestCheckResourceAttr(tfNode, "state.1.order", "2"),
resource.TestCheckResourceAttr(tfNode, "state.2.name", "New Active"),
resource.TestCheckResourceAttr(tfNode, "state.2.order", "3"),
resource.TestCheckResourceAttr(tfNode, "state.3.name", "New Closed"),
resource.TestCheckResourceAttr(tfNode, "state.3.order", "4"),
),
},
{
ResourceName: tfNode,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: getWorkItemTypeStateIdFunc(tfNode),
},
},
})
}

func TestAccWorkitemtrackingprocessWorkItemType_Inherited(t *testing.T) {
processName := testutils.GenerateResourceName()
tfNode := "azuredevops_workitemtrackingprocess_workitemtype.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testutils.PreCheck(t, nil) },
ProviderFactories: testutils.GetProviderFactories(),
CheckDestroy: testutils.CheckProcessDestroyed,
Steps: []resource.TestStep{
{
Config: inheritedWorkItemType(processName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(tfNode, "reference_name"),
),
},
{
ResourceName: tfNode,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: getWorkItemTypeStateIdFunc(tfNode),
},
},
})
}

func inheritedWorkItemType(processName string) string {
return fmt.Sprintf(`
%s

resource "azuredevops_workitemtrackingprocess_workitemtype" "test" {
name = "Bug"
process_id = azuredevops_workitemtrackingprocess_process.test.id
parent_work_item_reference_name = "Microsoft.VSTS.WorkItemTypes.Bug"
}
`, process(processName))
}

func TestAccWorkitemtrackingprocessWorkItemType_StatesForbiddenOnInherited(t *testing.T) {
workItemTypeName := testutils.GenerateWorkItemTypeName()
processName := testutils.GenerateResourceName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testutils.PreCheck(t, nil) },
ProviderFactories: testutils.GetProviderFactories(),
CheckDestroy: testutils.CheckProcessDestroyed,
Steps: []resource.TestStep{
{
Config: workItemTypeWithStatesAndParent(workItemTypeName, processName),
ExpectError: regexp.MustCompile(`state.*blocks are only valid on non-inherited work item types`),
},
},
})
}

func workItemTypeWithStatesAndParent(name, processName string) string {
return fmt.Sprintf(`
%s

resource "azuredevops_workitemtrackingprocess_workitemtype" "test" {
name = "%s"
process_id = azuredevops_workitemtrackingprocess_process.test.id
parent_work_item_reference_name = "Microsoft.VSTS.WorkItemTypes.Bug"

state {
name = "Custom"
color = "#3544ca"
state_category = "Proposed"
}
}
`, process(processName), name)
}

func TestAccWorkitemtrackingprocessWorkItemType_StatesRemovedFromConfig(t *testing.T) {
workItemTypeName := testutils.GenerateWorkItemTypeName()
processName := testutils.GenerateResourceName()
tfNode := "azuredevops_workitemtrackingprocess_workitemtype.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testutils.PreCheck(t, nil) },
ProviderFactories: testutils.GetProviderFactories(),
CheckDestroy: testutils.CheckProcessDestroyed,
Steps: []resource.TestStep{
{
Config: workItemTypeStatesWithNoChanges(workItemTypeName, processName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(tfNode, "state.#", "3"),
),
},
{
Config: basicWorkItemType(workItemTypeName, processName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(tfNode, "reference_name"),
),
},
},
})
}

// Azure DevOps rejects any Update against a Completed state (VS403093) even
// when the payload is identical, so the sync must skip those calls.
func TestAccWorkitemtrackingprocessWorkItemType_StatesWithNoChanges(t *testing.T) {
workItemTypeName := testutils.GenerateWorkItemTypeName()
processName := testutils.GenerateResourceName()
tfNode := "azuredevops_workitemtrackingprocess_workitemtype.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testutils.PreCheck(t, nil) },
ProviderFactories: testutils.GetProviderFactories(),
CheckDestroy: testutils.CheckProcessDestroyed,
Steps: []resource.TestStep{
{
Config: workItemTypeStatesWithNoChanges(workItemTypeName, processName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(tfNode, "state.#", "3"),
),
},
},
})
}

func workItemTypeStatesWithNoChanges(name, processName string) string {
return fmt.Sprintf(`
%s

resource "azuredevops_workitemtrackingprocess_workitemtype" "test" {
name = "%s"
process_id = azuredevops_workitemtrackingprocess_process.test.id

state {
name = "New"
color = "#3544ca"
state_category = "Proposed"
}

state {
name = "Active"
color = "#ff9d00"
state_category = "InProgress"
}

state {
name = "Closed"
color = "#339933"
state_category = "Completed"
}
}
`, process(processName), name)
}

func workItemTypeWithStates(name, processName string) string {
return fmt.Sprintf(`
%s

resource "azuredevops_workitemtrackingprocess_workitemtype" "test" {
name = "%s"
process_id = azuredevops_workitemtrackingprocess_process.test.id

state {
name = "New Active"
color = "#ff9d01"
state_category = "InProgress"
}

state {
name = "New Closed"
color = "#339933"
state_category = "Completed"
}
}
`, process(processName), name)
}

func workItemTypeWithStatesUpdated(name, processName string) string {
return fmt.Sprintf(`
%s

resource "azuredevops_workitemtrackingprocess_workitemtype" "test" {
name = "%s"
process_id = azuredevops_workitemtrackingprocess_process.test.id

state {
name = "New"
color = "#3544ca"
state_category = "Proposed"
}

state {
name = "New Active"
color = "#ff9d01"
state_category = "InProgress"
}

state {
name = "Active Order"
color = "#020100"
state_category = "InProgress"
}

state {
name = "New Closed"
color = "#339933"
state_category = "Completed"
}
}
`, process(processName), name)
}

func workItemTypeWithStatesChangingOrder(name, processName string) string {
return fmt.Sprintf(`
%s

resource "azuredevops_workitemtrackingprocess_workitemtype" "test" {
name = "%s"
process_id = azuredevops_workitemtrackingprocess_process.test.id

state {
name = "New"
color = "#3544ca"
state_category = "Proposed"
}

state {
name = "Active Order"
color = "#020100"
state_category = "InProgress"
}

state {
name = "New Active"
color = "#ff9d01"
state_category = "InProgress"
}

state {
name = "New Closed"
color = "#339933"
state_category = "Completed"
}
}
`, process(processName), name)
}

func basicWorkItemType(name string, processName string) string {
process := process(processName)
return fmt.Sprintf(`
Expand Down
26 changes: 26 additions & 0 deletions azuredevops/internal/service/workitemtrackingprocess/order.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package workitemtrackingprocess

import (
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/go-cty/cty/gocty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
Expand All @@ -23,3 +24,28 @@ func getOrder(d *schema.ResourceData) (*int, error) {
}
return &val, nil
}

// getOrderFromAttribute returns order on the i-th block of a list-shaped
// schema attribute if there is one defined, otherwise nil.
func getOrderFromAttribute(d *schema.ResourceData, attrName string, i int) (*int, error) {
rawPlan := d.GetRawPlan()
if !rawPlan.IsKnown() || rawPlan.IsNull() {
return nil, nil
}

attr := rawPlan.GetAttr(attrName)
if !attr.IsKnown() || attr.IsNull() || i >= attr.LengthInt() {
return nil, nil
}

order := attr.Index(cty.NumberIntVal(int64(i))).GetAttr("order")
if !order.IsKnown() || order.IsNull() {
return nil, nil
}

var val int
if err := gocty.FromCtyValue(order, &val); err != nil {
return nil, err
}
return &val, nil
}
Loading
Loading