// Copyright 2016-2022, Pulumi Corporation.
//
// 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 lifecycletest

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/blang/semver"

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

	"github.com/pulumi/pulumi/pkg/v3/display"
	. "github.com/pulumi/pulumi/pkg/v3/engine" //nolint:revive
	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
	pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
)

// Resource is an abstract representation of a resource graph
type Resource struct {
	t                   tokens.Type
	name                string
	children            []Resource
	props               resource.PropertyMap
	aliasURNs           []resource.URN
	aliases             []resource.Alias
	dependencies        []resource.URN
	parent              resource.URN
	deleteBeforeReplace bool

	aliasSpecs         bool
	grpcRequestHeaders map[string]string
}

// Ideally we'd get rid of this method, we probably shouldn't be talking in terms on `resource.Alias` in tests (they
// should be tested against the rpc protocol), and we should probably be using a stricter type in the engine.
func aliasesFromAliases(aliases []resource.Alias) []*pulumirpc.Alias {
	result := make([]*pulumirpc.Alias, len(aliases))
	for i, alias := range aliases {
		if alias.URN != "" {
			result[i] = &pulumirpc.Alias{
				Alias: &pulumirpc.Alias_Urn{
					Urn: string(alias.URN),
				},
			}
		} else {
			spec := &pulumirpc.Alias_Spec{
				Name:    alias.Name,
				Type:    alias.Type,
				Project: alias.Project,
				Stack:   alias.Stack,
			}

			if alias.Parent != "" {
				spec.Parent = &pulumirpc.Alias_Spec_ParentUrn{
					ParentUrn: string(alias.Parent),
				}
			} else if alias.NoParent {
				spec.Parent = &pulumirpc.Alias_Spec_NoParent{
					NoParent: alias.NoParent,
				}
			}

			result[i] = &pulumirpc.Alias{
				Alias: &pulumirpc.Alias_Spec_{
					Spec: spec,
				},
			}
		}
	}
	return result
}

func makeUrnAlias(urn string) *pulumirpc.Alias {
	return &pulumirpc.Alias{
		Alias: &pulumirpc.Alias_Urn{
			Urn: urn,
		},
	}
}

func makeSpecAlias(name, typ, project, stack string) *pulumirpc.Alias {
	return &pulumirpc.Alias{
		Alias: &pulumirpc.Alias_Spec_{
			Spec: &pulumirpc.Alias_Spec{
				Name:    name,
				Type:    typ,
				Project: project,
				Stack:   stack,
			},
		},
	}
}

func makeSpecAliasWithParent(name, typ, project, stack, parent string) *pulumirpc.Alias {
	return &pulumirpc.Alias{
		Alias: &pulumirpc.Alias_Spec_{
			Spec: &pulumirpc.Alias_Spec{
				Name:    name,
				Type:    typ,
				Project: project,
				Stack:   stack,
				Parent: &pulumirpc.Alias_Spec_ParentUrn{
					ParentUrn: parent,
				},
			},
		},
	}
}

func makeSpecAliasWithNoParent(name, typ, project, stack string, parent bool) *pulumirpc.Alias {
	return &pulumirpc.Alias{
		Alias: &pulumirpc.Alias_Spec_{
			Spec: &pulumirpc.Alias_Spec{
				Name:    name,
				Type:    typ,
				Project: project,
				Stack:   stack,
				Parent: &pulumirpc.Alias_Spec_NoParent{
					NoParent: parent,
				},
			},
		},
	}
}

func registerResources(t *testing.T, monitor *deploytest.ResourceMonitor, resources []Resource) error {
	for _, r := range resources {
		r := r
		_, err := monitor.RegisterResource(r.t, r.name, true, deploytest.ResourceOptions{
			Parent:              r.parent,
			Dependencies:        r.dependencies,
			Inputs:              r.props,
			DeleteBeforeReplace: &r.deleteBeforeReplace,
			AliasURNs:           r.aliasURNs,
			Aliases:             aliasesFromAliases(r.aliases),
			AliasSpecs:          r.aliasSpecs,
			GrpcRequestHeaders:  r.grpcRequestHeaders,
		})
		if err != nil {
			return err
		}
		err = registerResources(t, monitor, r.children)
		if err != nil {
			return err
		}
	}
	return nil
}

type updateProgramWithResourceFunc func(*deploy.Snapshot, []Resource, []display.StepOp, bool, string) *deploy.Snapshot

func createUpdateProgramWithResourceFuncForAliasTests(
	t *testing.T,
	loaders []*deploytest.ProviderLoader,
) updateProgramWithResourceFunc {
	t.Helper()

	return func(
		snap *deploy.Snapshot, resources []Resource, allowedOps []display.StepOp, expectFailure bool, name string,
	) *deploy.Snapshot {
		programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
			err := registerResources(t, monitor, resources)
			return err
		})
		hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
		p := &TestPlan{
			Options: TestUpdateOptions{T: t, HostF: hostF},
			Steps: []TestStep{
				{
					Op:            Update,
					ExpectFailure: expectFailure,
					Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
						events []Event, err error,
					) error {
						for _, event := range events {
							if event.Type == ResourcePreEvent {
								payload := event.Payload().(ResourcePreEventPayload)
								if payload.Metadata.Type == "pulumi:providers:pkgA" {
									continue
								}
								assert.Subset(t, allowedOps, []display.StepOp{payload.Metadata.Op})
							}
						}

						for _, entry := range entries {
							if entry.Step.Type() == "pulumi:providers:pkgA" {
								continue
							}
							switch entry.Kind {
							case JournalEntrySuccess:
								assert.Subset(t, allowedOps, []display.StepOp{entry.Step.Op()})
							case JournalEntryFailure:
								assert.Fail(t, "unexpected failure in journal")
							case JournalEntryBegin:
							case JournalEntryOutputs:
							}
						}

						return err
					},
				},
			},
		}
		return p.RunWithName(t, snap, name)
	}
}

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

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				// The `forcesReplacement` key forces replacement and all other keys can update in place
				DiffF: func(_ context.Context, req plugin.DiffRequest) (plugin.DiffResult, error) {
					replaceKeys := []resource.PropertyKey{}
					old, hasOld := req.OldOutputs["forcesReplacement"]
					new, hasNew := req.NewInputs["forcesReplacement"]
					if hasOld && !hasNew || hasNew && !hasOld || hasOld && hasNew && old.Diff(new) != nil {
						replaceKeys = append(replaceKeys, "forcesReplacement")
					}
					return plugin.DiffResult{ReplaceKeys: replaceKeys}, nil
				},
			}, nil
		}),
	}

	updateProgramWithResource := createUpdateProgramWithResourceFuncForAliasTests(t, loaders)

	snap := updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
	}}, []display.StepOp{deploy.OpCreate}, false, "t1")

	// Ensure that rename produces Same
	snap = updateProgramWithResource(snap, []Resource{{
		t:       "pkgA:index:t1",
		name:    "n2",
		aliases: []resource.Alias{{Name: "n1"}},
	}}, []display.StepOp{deploy.OpSame}, false, "t1-n2")

	// Ensure that rename produces Same with multiple aliases
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n3",
		aliases: []resource.Alias{
			{Name: "n2"},
			{Name: "n1"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "t1-n3")

	// Ensure that rename produces Same with multiple aliases (reversed)
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n3",
		aliases: []resource.Alias{
			{URN: "urn:pulumi:test::test::pkgA:index:t1::n2"},
			{Name: "n1"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "t1-n3-full")

	// Ensure that aliasing back to original name is okay
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		aliases: []resource.Alias{
			{URN: "urn:pulumi:test::test::pkgA:index:t1::n3"},
			{Name: "n2"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "t1-n1-alias-back")

	// Ensure that removing aliases is okay (once old names are gone from all snapshots)
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
	}}, []display.StepOp{deploy.OpSame}, false, "t1-n1-remove-aliases")

	// Ensure that changing the type works
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t2",
		name: "n1",
		aliases: []resource.Alias{
			{Type: "pkgA:index:t1"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "t2-n1")

	// Ensure that changing the type again works
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:othermod:t3",
		name: "n1",
		aliases: []resource.Alias{
			{Type: "pkgA:index:t1"},
			{Type: "pkgA:index:t2"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "t3-n1")

	// Ensure that order of aliases doesn't matter
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:othermod:t3",
		name: "n1",
		aliases: []resource.Alias{
			{Type: "pkgA:index:t1"},
			{Type: "pkgA:othermod:t3"},
			{Type: "pkgA:index:t2"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "t3-n1-order")

	// Ensure that removing aliases is okay (once old names are gone from all snapshots)
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:othermod:t3",
		name: "n1",
	}}, []display.StepOp{deploy.OpSame}, false, "t3-remove-aliases")

	// Ensure that changing everything (including props) leads to update not delete and re-create
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t4",
		name: "n2",
		props: resource.PropertyMap{
			resource.PropertyKey("x"): resource.NewNumberProperty(42),
		},
		aliases: []resource.Alias{
			{Type: "pkgA:othermod:t3", Name: "n1"},
		},
	}}, []display.StepOp{deploy.OpUpdate}, false, "t4-n2")

	// Ensure that changing everything again (including props) leads to update not delete and re-create
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t5",
		name: "n3",
		props: resource.PropertyMap{
			resource.PropertyKey("x"): resource.NewNumberProperty(1000),
		},
		aliases: []resource.Alias{
			{Type: "pkgA:index:t4", Name: "n2"},
		},
	}}, []display.StepOp{deploy.OpUpdate}, false, "t5-n3")

	// Ensure that changing a forceNew property while also changing type and name leads to replacement not delete+create
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t6",
		name: "n4",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1000),
		},
		aliases: []resource.Alias{
			{Type: "pkgA:index:t5", Name: "n3"},
		},
	}}, []display.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced}, false, "t6-n4")

	// Ensure that changing a forceNew property and deleteBeforeReplace while also changing type and name leads to
	// replacement not delete+create
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t7",
		name: "n5",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(999),
		},
		deleteBeforeReplace: true,
		aliases: []resource.Alias{
			{Type: "pkgA:index:t6", Name: "n4"},
		},
	}}, []display.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced}, false, "t7-n5")

	// Start again - this time with two resources with depends on relationship
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1),
		},
		deleteBeforeReplace: true,
	}, {
		t:            "pkgA:index:t2",
		name:         "n2",
		dependencies: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1::n1"},
	}}, []display.StepOp{deploy.OpCreate}, false, "t1-start-again")

	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1-new",
		name: "n1-new",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(2),
		},
		deleteBeforeReplace: true,
		aliases: []resource.Alias{
			{Type: "pkgA:index:t1", Name: "n1"},
		},
	}, {
		t:            "pkgA:index:t2-new",
		name:         "n2-new",
		dependencies: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1-new::n1-new"},
		aliases: []resource.Alias{
			{Type: "pkgA:index:t2", Name: "n2"},
		},
	}}, []display.StepOp{deploy.OpSame, deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced},
		false, "t1-new-t2-new")

	// Start again - this time with two resources with parent relationship
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1),
		},
		deleteBeforeReplace: true,
	}, {
		t:      "pkgA:index:t2",
		name:   "n2",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1::n1"),
	}}, []display.StepOp{deploy.OpCreate}, false, "t1-t2-start-again")

	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1-new",
		name: "n1-new",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(2),
		},
		deleteBeforeReplace: true,
		aliases: []resource.Alias{
			{Type: "pkgA:index:t1", Name: "n1"},
		},
	}, {
		t:      "pkgA:index:t2-new",
		name:   "n2-new",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-new::n1-new"),
		aliases: []resource.Alias{
			{Type: "pkgA:index:t2", Name: "n2"},
		},
	}}, []display.StepOp{deploy.OpSame, deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced},
		false, "t1-new-t2-new-parent")

	// ensure failure when different resources use duplicate aliases
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n2",
		aliases: []resource.Alias{
			{Name: "n1"},
		},
	}, {
		t:    "pkgA:index:t2",
		name: "n3",
		aliases: []resource.Alias{
			{Type: "pkgA:index:t1", Name: "n1"},
		},
	}}, []display.StepOp{deploy.OpCreate}, true, "t1-fail-duplicate")

	// ensure different resources can use different aliases
	_ = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		aliases: []resource.Alias{
			{Name: "n1"},
		},
	}, {
		t:    "pkgA:index:t2",
		name: "n2",
		aliases: []resource.Alias{
			{Type: "index:t1"},
		},
	}}, []display.StepOp{deploy.OpCreate}, false, "different-aliases")

	// ensure that aliases of parents of parents resolves correctly
	// first create a chain of resources such that we have n1 -> n1-sub -> n1-sub-sub
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
	}, {
		t:      "pkgA:index:t2",
		name:   "n1-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1::n1"),
	}, {
		t:      "pkgA:index:t3",
		name:   "n1-sub-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t2::n1-sub"),
	}}, []display.StepOp{deploy.OpCreate}, false, "parents")

	// Now change n1's name and type
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1-new",
		name: "n1-new",
		aliases: []resource.Alias{
			{Type: "pkgA:index:t1", Name: "n1"},
		},
	}, {
		t:      "pkgA:index:t2",
		name:   "n1-new-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-new::n1-new"),
		aliases: []resource.Alias{
			{Type: "pkgA:index:t2", Name: "n1-sub"},
		},
	}, {
		t:      "pkgA:index:t3",
		name:   "n1-new-sub-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-new$pkgA:index:t2::n1-new-sub"),
		aliases: []resource.Alias{
			{Type: "pkgA:index:t3", Name: "n1-sub-sub"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "n1-name-change")

	// Test catastrophic multiplication out of aliases doesn't crash out of memory
	// first create a chain of resources such that we have n1 -> n1-sub -> n1-sub-sub
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1-v0",
		name: "n1",
	}, {
		t:      "pkgA:index:t2-v0",
		name:   "n1-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-v0::n1"),
	}, {
		t:      "pkgA:index:t3",
		name:   "n1-sub-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-v0$pkgA:index:t2-v0::n1-sub"),
	}}, []display.StepOp{deploy.OpCreate}, false, "multiplication")

	// Now change n1's name and type and n2's type, but also add a load of aliases and pre-multiply them out
	// before sending to the engine
	n1Aliases := make([]resource.Alias, 0)
	n2Aliases := make([]resource.Alias, 0)
	n3Aliases := make([]resource.Alias, 0)
	for i := 0; i < 100; i++ {
		n1Aliases = append(n1Aliases, resource.Alias{URN: resource.URN(
			fmt.Sprintf("urn:pulumi:test::test::pkgA:index:t1-v%d::n1", i),
		)})

		for j := 0; j < 10; j++ {
			n2Aliases = append(n2Aliases, resource.Alias{
				URN: resource.URN(fmt.Sprintf("urn:pulumi:test::test::pkgA:index:t1-v%d$pkgA:index:t2-v%d::n1-sub", i, j)),
			})
			n3Aliases = append(n3Aliases, resource.Alias{
				Name:    "n1-sub-sub",
				Type:    fmt.Sprintf("pkgA:index:t1-v%d$pkgA:index:t2-v%d$pkgA:index:t3", i, j),
				Stack:   "test",
				Project: "test",
			})
		}
	}

	snap = updateProgramWithResource(snap, []Resource{{
		t:       "pkgA:index:t1-v100",
		name:    "n1-new",
		aliases: n1Aliases,
	}, {
		t:       "pkgA:index:t2-v10",
		name:    "n1-new-sub",
		parent:  resource.URN("urn:pulumi:test::test::pkgA:index:t1-v100::n1-new"),
		aliases: n2Aliases,
	}, {
		t:       "pkgA:index:t3",
		name:    "n1-new-sub-sub",
		parent:  resource.URN("urn:pulumi:test::test::pkgA:index:t1-v100$pkgA:index:t2-v10::n1-new-sub"),
		aliases: n3Aliases,
	}}, []display.StepOp{deploy.OpSame}, false, "many-alaises")

	var err error
	_, err = snap.NormalizeURNReferences()
	assert.NoError(t, err)

	// Start again with a parent and child.
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:parent",
		name: "parent",
	}, {
		t:      "pkgA:index:child",
		parent: "urn:pulumi:test::test::pkgA:index:parent::parent",
		name:   "child",
	}}, []display.StepOp{deploy.OpCreate}, false, "start-again-parent-child")

	// Ensure renaming just the child produces Same
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:parent",
		name: "parent",
	}, {
		t:      "pkgA:index:child",
		parent: "urn:pulumi:test::test::pkgA:index:parent::parent",
		name:   "childnew",
		aliases: []resource.Alias{
			{Name: "child"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "child-same")

	// Ensure changing just the child's type produces Same
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:parent",
		name: "parent",
	}, {
		t:      "pkgA:index:child2",
		parent: "urn:pulumi:test::test::pkgA:index:parent::parent",
		name:   "childnew",
		aliases: []resource.Alias{
			{Type: "pkgA:index:child"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "change-child-same")

	// Start again with multiple nested children.
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "parent",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1::parent",
		name:   "sub",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t1::sub",
		name:   "sub-sub",
	}}, []display.StepOp{deploy.OpCreate}, false, "nested-children")

	// Ensure renaming the bottom child produces Same.
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "parent",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1::parent",
		name:   "sub",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t1::sub",
		name:   "sub-sub-new",
		aliases: []resource.Alias{
			{Name: "sub-sub"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "rename-bottom-children")

	// Start again with two resources with no relationship.
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "one",
	}, {
		t:    "pkgA:index:t2",
		name: "two",
	}}, []display.StepOp{deploy.OpCreate}, false, "no-relationship")

	// Now make "two" a child of "one" ensuring no changes.
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "one",
	}, {
		t:      "pkgA:index:t2",
		parent: "urn:pulumi:test::test::pkgA:index:t1::one",
		name:   "two",
		aliases: []resource.Alias{
			{NoParent: true},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "make-children")

	// Now remove the parent relationship.
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "one",
	}, {
		t:    "pkgA:index:t2",
		name: "two",
		aliases: []resource.Alias{
			{Parent: "urn:pulumi:test::test::pkgA:index:t1::one"},
		},
	}}, []display.StepOp{deploy.OpSame}, false, "remove-parent-relationship")
}

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

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{}, nil
		}),
	}

	updateProgramWithResource := createUpdateProgramWithResourceFuncForAliasTests(t, loaders)

	tests := []struct {
		name               string
		aliasSpecs         bool
		grpcRequestHeaders map[string]string
		noParentAlias      resource.Alias
	}{
		{
			name:               "Old Node SDK",
			grpcRequestHeaders: map[string]string{"pulumi-runtime": "nodejs"},
			// Old Node.js SDKs set Parent to "" rather than setting NoParent to true,
			noParentAlias: resource.Alias{Parent: ""},
		},
		{
			name:               "New Node SDK",
			grpcRequestHeaders: map[string]string{"pulumi-runtime": "nodejs"},
			// Indicate we're sending alias specs correctly.
			aliasSpecs: true,
			// Properly set NoParent to true.
			noParentAlias: resource.Alias{NoParent: true},
		},
		{
			name: "Unknown SDK",
			// Properly set NoParent to true.
			noParentAlias: resource.Alias{NoParent: true},
		},
	}
	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			snap := updateProgramWithResource(nil, []Resource{{
				t:                  "pkgA:index:t1",
				name:               "one",
				aliasSpecs:         tt.aliasSpecs,
				grpcRequestHeaders: tt.grpcRequestHeaders,
			}, {
				t:                  "pkgA:index:t2",
				name:               "two",
				aliasSpecs:         tt.aliasSpecs,
				grpcRequestHeaders: tt.grpcRequestHeaders,
			}}, []display.StepOp{deploy.OpCreate}, false, "one-two")

			// Now make "two" a child of "one" ensuring no changes.
			snap = updateProgramWithResource(snap, []Resource{{
				t:                  "pkgA:index:t1",
				name:               "one",
				aliasSpecs:         tt.aliasSpecs,
				grpcRequestHeaders: tt.grpcRequestHeaders,
			}, {
				t:      "pkgA:index:t2",
				parent: "urn:pulumi:test::test::pkgA:index:t1::one",
				name:   "two",
				aliases: []resource.Alias{
					tt.noParentAlias,
				},
				aliasSpecs:         tt.aliasSpecs,
				grpcRequestHeaders: tt.grpcRequestHeaders,
			}}, []display.StepOp{deploy.OpSame}, false, "make-two-child-of-one")

			// Now remove the parent relationship.
			_ = updateProgramWithResource(snap, []Resource{{
				t:    "pkgA:index:t1",
				name: "one",
			}, {
				t:    "pkgA:index:t2",
				name: "two",
				aliases: []resource.Alias{
					{Parent: "urn:pulumi:test::test::pkgA:index:t1::one"},
				},
				aliasSpecs:         tt.aliasSpecs,
				grpcRequestHeaders: tt.grpcRequestHeaders,
			}}, []display.StepOp{deploy.OpSame}, false, "remove-relationship-of-two-to-one")
		})
	}
}

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

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				// The `forcesReplacement` key forces replacement and all other keys can update in place
				DiffF: func(_ context.Context, req plugin.DiffRequest) (plugin.DiffResult, error) {
					replaceKeys := []resource.PropertyKey{}
					old, hasOld := req.OldOutputs["forcesReplacement"]
					new, hasNew := req.NewInputs["forcesReplacement"]
					if hasOld && !hasNew || hasNew && !hasOld || hasOld && hasNew && old.Diff(new) != nil {
						replaceKeys = append(replaceKeys, "forcesReplacement")
					}
					return plugin.DiffResult{ReplaceKeys: replaceKeys}, nil
				},
			}, nil
		}),
	}

	updateProgramWithResource := createUpdateProgramWithResourceFuncForAliasTests(t, loaders)

	snap := updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-t1-n1")

	// Ensure that rename produces Same
	snap = updateProgramWithResource(snap, []Resource{{
		t:         "pkgA:index:t1",
		name:      "n2",
		aliasURNs: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1::n1"},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-t1-n2")

	// Ensure that rename produces Same with multiple aliases
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n3",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
			"urn:pulumi:test::test::pkgA:index:t1::n2",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-t1-n3")

	// Ensure that rename produces Same with multiple aliases (reversed)
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n3",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n2",
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-t1-n3-rename")

	// Ensure that aliasing back to original name is okay
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n3",
			"urn:pulumi:test::test::pkgA:index:t1::n2",
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-alias-original")

	// Ensure that removing aliases is okay (once old names are gone from all snapshots)
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
	}}, []display.StepOp{deploy.OpSame}, false, "urn-remove-alias")

	// Ensure that changing the type works
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t2",
		name: "n1",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-t2-n1")

	// Ensure that changing the type again works
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:othermod:t3",
		name: "n1",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
			"urn:pulumi:test::test::pkgA:index:t2::n1",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-change-type")

	// Ensure that order of aliases doesn't matter
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:othermod:t3",
		name: "n1",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
			"urn:pulumi:test::test::pkgA:othermod:t3::n1",
			"urn:pulumi:test::test::pkgA:index:t2::n1",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-alias-order")

	// Ensure that removing aliases is okay (once old names are gone from all snapshots)
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:othermod:t3",
		name: "n1",
	}}, []display.StepOp{deploy.OpSame}, false, "urn-remove-alias-2")

	// Ensure that changing everything (including props) leads to update not delete and re-create
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t4",
		name: "n2",
		props: resource.PropertyMap{
			resource.PropertyKey("x"): resource.NewNumberProperty(42),
		},
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:othermod:t3::n1",
		},
	}}, []display.StepOp{deploy.OpUpdate}, false, "urn-t4-n2")

	// Ensure that changing everything again (including props) leads to update not delete and re-create
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t5",
		name: "n3",
		props: resource.PropertyMap{
			resource.PropertyKey("x"): resource.NewNumberProperty(1000),
		},
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t4::n2",
		},
	}}, []display.StepOp{deploy.OpUpdate}, false, "urn-change-everything-again")

	// Ensure that changing a forceNew property while also changing type and name leads to replacement not delete+create
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t6",
		name: "n4",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1000),
		},
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t5::n3",
		},
	}}, []display.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced}, false, "urn-t6-n4")

	// Ensure that changing a forceNew property and deleteBeforeReplace while also changing type and name leads to
	// replacement not delete+create
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t7",
		name: "n5",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(999),
		},
		deleteBeforeReplace: true,
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t6::n4",
		},
	}}, []display.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced}, false, "urn-force-new")

	// Start again - this time with two resources with depends on relationship
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1),
		},
		deleteBeforeReplace: true,
	}, {
		t:            "pkgA:index:t2",
		name:         "n2",
		dependencies: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1::n1"},
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-depends-relationship")

	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1-new",
		name: "n1-new",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(2),
		},
		deleteBeforeReplace: true,
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}, {
		t:            "pkgA:index:t2-new",
		name:         "n2-new",
		dependencies: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1-new::n1-new"},
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t2::n2",
		},
	}}, []display.StepOp{deploy.OpSame, deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced},
		false, "urn-depends-relationship-2")

	// Start again - this time with two resources with parent relationship
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1),
		},
		deleteBeforeReplace: true,
	}, {
		t:      "pkgA:index:t2",
		name:   "n2",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1::n1"),
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-parent-relationship")

	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1-new",
		name: "n1-new",
		props: resource.PropertyMap{
			resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(2),
		},
		deleteBeforeReplace: true,
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}, {
		t:      "pkgA:index:t2-new",
		name:   "n2-new",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-new::n1-new"),
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t2::n2",
		},
	}}, []display.StepOp{deploy.OpSame, deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced},
		false, "urn-parent-relationship-2")

	// ensure failure when different resources use duplicate aliases
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "n2",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}, {
		t:    "pkgA:index:t2",
		name: "n3",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}}, []display.StepOp{deploy.OpCreate}, true, "urn-fail-duplicate")

	// ensure different resources can use different aliases
	_ = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}, {
		t:    "pkgA:index:t2",
		name: "n2",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n2",
		},
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-different-aliases")

	// ensure that aliases of parents of parents resolves correctly
	// first create a chain of resources such that we have n1 -> n1-sub -> n1-sub-sub
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "n1",
	}, {
		t:      "pkgA:index:t2",
		name:   "n1-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1::n1"),
	}, {
		t:      "pkgA:index:t3",
		name:   "n1-sub-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t2::n1-sub"),
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-parent-alias-resolution")

	// Now change n1's name and type
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1-new",
		name: "n1-new",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1::n1",
		},
	}, {
		t:      "pkgA:index:t2",
		name:   "n1-new-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-new::n1-new"),
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t2::n1-sub",
		},
	}, {
		t:      "pkgA:index:t3",
		name:   "n1-new-sub-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-new$pkgA:index:t2::n1-new-sub"),
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t2$pkgA:index:t3::n1-sub-sub",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-n1-change-name-and-type")

	// Test catastrophic multiplication out of aliases doesn't crash out of memory
	// first create a chain of resources such that we have n1 -> n1-sub -> n1-sub-sub
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1-v0",
		name: "n1",
	}, {
		t:      "pkgA:index:t2-v0",
		name:   "n1-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-v0::n1"),
	}, {
		t:      "pkgA:index:t3",
		name:   "n1-sub-sub",
		parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-v0$pkgA:index:t2-v0::n1-sub"),
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-multiplication")

	// Now change n1's name and type and n2's type, but also add a load of aliases and pre-multiply them out
	// before sending to the engine
	n1Aliases := make([]resource.URN, 0)
	n2Aliases := make([]resource.URN, 0)
	n3Aliases := make([]resource.URN, 0)
	for i := 0; i < 100; i++ {
		n1Aliases = append(n1Aliases, resource.URN(
			fmt.Sprintf("urn:pulumi:test::test::pkgA:index:t1-v%d::n1", i)))

		for j := 0; j < 10; j++ {
			n2Aliases = append(n2Aliases, resource.URN(
				fmt.Sprintf("urn:pulumi:test::test::pkgA:index:t1-v%d$pkgA:index:t2-v%d::n1-sub", i, j)))

			n3Aliases = append(n3Aliases, resource.URN(
				fmt.Sprintf("urn:pulumi:test::test::pkgA:index:t1-v%d$pkgA:index:t2-v%d$pkgA:index:t3::n1-sub-sub", i, j)))
		}
	}

	snap = updateProgramWithResource(snap, []Resource{{
		t:         "pkgA:index:t1-v100",
		name:      "n1-new",
		aliasURNs: n1Aliases,
	}, {
		t:         "pkgA:index:t2-v10",
		name:      "n1-new-sub",
		parent:    resource.URN("urn:pulumi:test::test::pkgA:index:t1-v100::n1-new"),
		aliasURNs: n2Aliases,
	}, {
		t:         "pkgA:index:t3",
		name:      "n1-new-sub-sub",
		parent:    resource.URN("urn:pulumi:test::test::pkgA:index:t1-v100$pkgA:index:t2-v10::n1-new-sub"),
		aliasURNs: n3Aliases,
	}}, []display.StepOp{deploy.OpSame}, false, "urn-multiple-aliases")

	var err error
	_, err = snap.NormalizeURNReferences()
	assert.NoError(t, err)

	// Start again with a parent and child.
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:parent",
		name: "parent",
	}, {
		t:      "pkgA:index:child",
		parent: "urn:pulumi:test::test::pkgA:index:parent::parent",
		name:   "child",
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-parent-child")

	// Ensure renaming just the child produces Same
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:parent",
		name: "parent",
	}, {
		t:      "pkgA:index:child",
		parent: "urn:pulumi:test::test::pkgA:index:parent::parent",
		name:   "childnew",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:parent$pkgA:index:child::child",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-rename-child")

	// Ensure changing just the child's type produces Same
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:parent",
		name: "parent",
	}, {
		t:      "pkgA:index:child2",
		parent: "urn:pulumi:test::test::pkgA:index:parent::parent",
		name:   "childnew",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:parent$pkgA:index:child::childnew",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-change-child-type")

	// Start again with multiple nested children.
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "parent",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1::parent",
		name:   "sub",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t1::sub",
		name:   "sub-sub",
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-nested-children")

	// Ensure renaming the bottom child produces Same.
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "parent",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1::parent",
		name:   "sub",
	}, {
		t:      "pkgA:index:t1",
		parent: "urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t1::sub",
		name:   "sub-sub-new",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t1$pkgA:index:t1::sub-sub",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-rename-bottom-child")

	// Start again with two resources with no relationship.
	snap = updateProgramWithResource(nil, []Resource{{
		t:    "pkgA:index:t1",
		name: "one",
	}, {
		t:    "pkgA:index:t2",
		name: "two",
	}}, []display.StepOp{deploy.OpCreate}, false, "urn-two-resources-no-relationship")

	// Now make "two" a child of "one" ensuring no changes.
	snap = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "one",
	}, {
		t:      "pkgA:index:t2",
		parent: "urn:pulumi:test::test::pkgA:index:t1::one",
		name:   "two",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t2::two",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-make-two-child-of-one")

	// Now remove the parent relationship.
	_ = updateProgramWithResource(snap, []Resource{{
		t:    "pkgA:index:t1",
		name: "one",
	}, {
		t:    "pkgA:index:t2",
		name: "two",
		aliasURNs: []resource.URN{
			"urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t2::two",
		},
	}}, []display.StepOp{deploy.OpSame}, false, "urn-remove-parent-relationship")
}

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

	// This is a test for https://github.com/pulumi/pulumi/issues/11173
	// to check that we don't allow resource aliases to refer to other resources.
	// That is if you have A, then try and add B saying it's alias is A we should error that's a duplicate.
	// We need to be careful that we handle this regardless of the order we send the RegisterResource requests for A and B.

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
					return plugin.CreateResponse{
						ID:         "created-id",
						Properties: req.Properties,
						Status:     resource.StatusOK,
					}, nil
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	mode := 0
	programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		switch mode {
		case 0:
			// Default case, just make resA
			_, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resA",
				true,
				deploytest.ResourceOptions{})
			assert.NoError(t, err)

		case 1:
			// First test case, try and create a new B that aliases to A. First make the A like normal...
			_, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resA",
				true,
				deploytest.ResourceOptions{})
			assert.NoError(t, err)

			// ... then make B with an alias, it should error
			_, err = monitor.RegisterResource(
				"pkgA:m:typA",
				"resB",
				true,
				deploytest.ResourceOptions{
					Aliases: []*pulumirpc.Alias{
						makeSpecAlias("resA", "", "", ""),
					},
				})
			assert.Error(t, err)

		case 2:
			// Second test case, try and create a new B that aliases to A. First make the B with an alias...
			_, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resB",
				true,
				deploytest.ResourceOptions{
					Aliases: []*pulumirpc.Alias{
						makeSpecAlias("resA", "", "", ""),
					},
				})
			assert.NoError(t, err)

			// ... then try to make the A like normal. It should error that it's already been aliased away
			_, err = monitor.RegisterResource(
				"pkgA:m:typA",
				"resA",
				true,
				deploytest.ResourceOptions{})
			assert.Error(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{T: t, HostF: hostF},
	}

	project := p.GetProject()

	// Run an update to create the starting A resource
	snap, err := TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 2)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[1].URN)

	// Set mode to try and create A then a B that aliases to it, this should fail
	mode = 1
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
	assert.Error(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 2)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[1].URN)

	// Set mode to try and create B first then a A, this should fail
	mode = 2
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "2")
	assert.Error(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 2)
	// Because we made the B first that's what should end up in the state file
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resB"), snap.Resources[1].URN)
}

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

	// This is a test for https://github.com/pulumi/pulumi/issues/13848
	// to check that a resource's URN is used first when looking for old resources in the state
	// rather than aliases, and that we don't end up with a corrupt state after an update.

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
					return plugin.CreateResponse{
						ID:         "created-id",
						Properties: req.Properties,
						Status:     resource.StatusOK,
					}, nil
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	mode := 0
	programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		switch mode {
		case 0:
			// Default case, make "resA", "resB with resA as its parent", and "resB with no parent".
			respA, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resA",
				true,
				deploytest.ResourceOptions{})
			assert.NoError(t, err)

			_, err = monitor.RegisterResource(
				"pkgA:m:typA",
				"resB",
				true,
				deploytest.ResourceOptions{Parent: respA.URN})
			assert.NoError(t, err)

			_, err = monitor.RegisterResource(
				"pkgA:m:typA",
				"resB",
				true,
				deploytest.ResourceOptions{})
			assert.NoError(t, err)

		case 1:
			// Next case, make "resA" and "resB with no parent and alias to have resA as its parent".
			respA, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resA",
				true,
				deploytest.ResourceOptions{})
			assert.NoError(t, err)

			_, err = monitor.RegisterResource(
				"pkgA:m:typA",
				"resB",
				true,
				deploytest.ResourceOptions{
					Aliases: []*pulumirpc.Alias{
						makeSpecAliasWithParent("", "", "", "", string(respA.URN)),
					},
				},
			)
			assert.NoError(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{T: t, HostF: hostF},
	}

	project := p.GetProject()

	// Run an update for initial state with "resA", "resB with resA as its parent", and "resB with no parent".
	snap, err := TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 4)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[1].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA$pkgA:m:typA::resB"), snap.Resources[2].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resB"), snap.Resources[3].URN)

	// Run the next case, with "resA" and "resB with no parent and alias to have resA as its parent".
	mode = 1
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 3)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[1].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resB"), snap.Resources[2].URN)
	assert.Len(t, snap.Resources[2].Aliases, 0)
}

func TestComponentToCustomUpdate(t *testing.T) {
	// Test for https://github.com/pulumi/pulumi/issues/12550, check that if we change a component resource
	// into a custom resource the engine handles that best it can. This depends on the provider being able to
	// cope with the component state being passed as custom state.

	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
					id := resource.ID("")
					if !req.Preview {
						id = resource.ID("1")
					}
					return plugin.CreateResponse{
						ID:         id,
						Properties: req.Properties,
						Status:     resource.StatusOK,
					}, nil
				},
				DeleteF: func(context.Context, plugin.DeleteRequest) (plugin.DeleteResponse, error) {
					return plugin.DeleteResponse{}, nil
				},
				DiffF: func(context.Context, plugin.DiffRequest) (plugin.DiffResult, error) {
					return plugin.DiffResult{}, nil
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	insA := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	createA := func(monitor *deploytest.ResourceMonitor) {
		_, err := monitor.RegisterResource("prog::myType", "resA", false, deploytest.ResourceOptions{
			Inputs: insA,
		})
		assert.NoError(t, err)
	}

	programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		createA(monitor)
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{T: t, HostF: hostF},
	}

	project := p.GetProject()

	// Run an update to create the resources
	snap, err := TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 1)
	assert.Equal(t, tokens.Type("prog::myType"), snap.Resources[0].Type)
	assert.False(t, snap.Resources[0].Custom)

	// Now update A from a component to custom with an alias
	createA = func(monitor *deploytest.ResourceMonitor) {
		_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: insA,
			Aliases: []*pulumirpc.Alias{
				makeSpecAlias("", "prog::myType", "", ""),
			},
		})
		assert.NoError(t, err)
	}
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
	// Assert that A is now a custom
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	// Now two because we'll have a provider now
	assert.Len(t, snap.Resources, 2)
	assert.Equal(t, tokens.Type("pkgA:m:typA"), snap.Resources[1].Type)
	assert.True(t, snap.Resources[1].Custom)

	// Now update A back to a component (with an alias)
	createA = func(monitor *deploytest.ResourceMonitor) {
		_, err := monitor.RegisterResource("prog::myType", "resA", false, deploytest.ResourceOptions{
			Inputs: insA,
			Aliases: []*pulumirpc.Alias{
				makeSpecAlias("", "pkgA:m:typA", "", ""),
			},
		})
		assert.NoError(t, err)
	}
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "2")
	// Assert that A is now a custom
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	// Back to one because the provider should have been cleaned up as well
	assert.Len(t, snap.Resources, 1)
	assert.Equal(t, tokens.Type("prog::myType"), snap.Resources[0].Type)
	assert.False(t, snap.Resources[0].Custom)
}

func TestParentAlias(t *testing.T) {
	// Test for https://github.com/pulumi/pulumi/issues/13324, check that if we change a parent resource which
	// is also aliased at the same time that we track this correctly.

	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
					id := resource.ID("")
					if !req.Preview {
						id = resource.ID("1")
					}
					return plugin.CreateResponse{
						ID:         id,
						Properties: req.Properties,
						Status:     resource.StatusOK,
					}, nil
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	firstRun := true
	programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		respA, err := monitor.RegisterResource(
			"prog:index:myStandardType", "resA", false, deploytest.ResourceOptions{})
		assert.NoError(t, err)

		if firstRun {
			respB, err := monitor.RegisterResource("prog:index:myType", "resB", false, deploytest.ResourceOptions{})
			assert.NoError(t, err)

			_, err = monitor.RegisterResource("pkgA:m:typA", "resC", true, deploytest.ResourceOptions{
				Parent: respB.URN,
			})
			assert.NoError(t, err)
		} else {
			respB, err := monitor.RegisterResource("prog:index:myType", "resB", false, deploytest.ResourceOptions{
				Parent: respA.URN,
				Aliases: []*pulumirpc.Alias{
					makeSpecAliasWithNoParent("", "", "", "", true),
				},
			})
			assert.NoError(t, err)

			_, err = monitor.RegisterResource("pkgA:m:typA", "resC", true, deploytest.ResourceOptions{
				Parent: respA.URN,
				Aliases: []*pulumirpc.Alias{
					makeSpecAliasWithParent("", "", "", "", string(respB.URN)),
				},
			})
			assert.NoError(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{T: t, HostF: hostF},
	}

	project := p.GetProject()

	// Run an update to create the resources
	snap, err := TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 4)

	// Now run again with the rearranged parents, we don't expect to see any replaces
	firstRun = false
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
		func(project workspace.Project, target deploy.Target,
			entries JournalEntries, events []Event, err error,
		) error {
			for _, entry := range entries {
				assert.Equal(t, deploy.OpSame, entry.Step.Op())
			}
			return err
		}, "1")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 4)
}

func TestEmptyParentAlias(t *testing.T) {
	// Test for backwards compatibility with Python, an alias that sets parent explicitly to "" should be treated the
	// same as not setting parent at all.

	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
					id := resource.ID("")
					if !req.Preview {
						id = resource.ID("1")
					}
					return plugin.CreateResponse{
						ID:         id,
						Properties: req.Properties,
						Status:     resource.StatusOK,
					}, nil
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	firstRun := true
	programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		respA, err := monitor.RegisterResource(
			"prog:index:myStandardType", "resA", false, deploytest.ResourceOptions{})
		assert.NoError(t, err)

		if firstRun {
			_, err := monitor.RegisterResource("prog:index:myType", "resB", false, deploytest.ResourceOptions{
				Parent: respA.URN,
			})
			assert.NoError(t, err)
		} else {
			_, err := monitor.RegisterResource("prog:index:myType", "resC", false, deploytest.ResourceOptions{
				Parent: respA.URN,
				Aliases: []*pulumirpc.Alias{
					makeSpecAliasWithParent("resB", "", "", "", ""),
				},
			})
			assert.NoError(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{T: t, HostF: hostF},
	}

	project := p.GetProject()

	// Run an update to create the resources
	snap, err := TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
	require.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 2)

	// Now run again with the rearranged parents, we don't expect to see any replaces
	firstRun = false
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
		func(project workspace.Project, target deploy.Target,
			entries JournalEntries, events []Event, err error,
		) error {
			for _, entry := range entries {
				assert.Equal(t, deploy.OpSame, entry.Step.Op())
			}
			return err
		}, "1")
	require.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 2)
}

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

	// This is a test for https://github.com/pulumi/pulumi/issues/13903 to check that if a component is
	// aliased the internal resources follow it across a split deployment.

	mode := 0
	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
					// We should only create things in the first pass
					assert.Equal(t, 0, mode, "%s tried to create but should be aliased", req.URN)

					return plugin.CreateResponse{
						ID:         "created-id",
						Properties: req.Properties,
						Status:     resource.StatusOK,
					}, nil
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		switch mode {
		case 0:
			// Default case, make "resA", and "resB" a component with "resA" as its parent and "resC" in the component
			respA, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resA",
				true,
				deploytest.ResourceOptions{})
			assert.NoError(t, err)

			respB, err := monitor.RegisterResource(
				"pkgA:m:typB",
				"resB",
				false,
				deploytest.ResourceOptions{
					Parent: respA.URN,
				})
			assert.NoError(t, err)

			_, err = monitor.RegisterResource(
				"pkgA:m:typC",
				"resC",
				true,
				deploytest.ResourceOptions{
					Parent: respB.URN,
				})
			assert.NoError(t, err)

		case 1:
			// Delete "resA" and re-parent "resB" to the root but then fail before getting to resC.
			_, err := monitor.RegisterResource(
				"pkgA:m:typB",
				"resB",
				false,
				deploytest.ResourceOptions{
					AliasURNs: []resource.URN{
						"urn:pulumi:test::test::pkgA:m:typA$pkgA:m:typB::resB",
					},
				})
			assert.NoError(t, err)

			return errors.New("something went bang")

		case 2:
			// Update again but this time succeed and register C.
			respB, err := monitor.RegisterResource(
				"pkgA:m:typB",
				"resB",
				false,
				deploytest.ResourceOptions{
					AliasURNs: []resource.URN{
						"urn:pulumi:test::test::pkgA:m:typA$pkgA:m:typB::resB",
					},
				})
			assert.NoError(t, err)

			_, err = monitor.RegisterResource(
				"pkgA:m:typC",
				"resC",
				true,
				deploytest.ResourceOptions{
					Parent: respB.URN,
				})
			assert.NoError(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{T: t, HostF: hostF},
	}

	project := p.GetProject()

	// Run an update for initial state with "resA", "resB", and "resC".
	snap, err := TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 4)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[1].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA$pkgA:m:typB::resB"), snap.Resources[2].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[2].Parent)
	assert.Equal(t,
		resource.URN("urn:pulumi:test::test::pkgA:m:typA$pkgA:m:typB$pkgA:m:typC::resC"),
		snap.Resources[3].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA$pkgA:m:typB::resB"), snap.Resources[3].Parent)

	// Run the next case, resB should be re-parented to the root. A should still be left (because we couldn't
	// tell it needed to delete due to the error), C should have it's old URN but new parent because it wasn't
	// registered.
	mode = 1
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
	assert.Error(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 4)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typB::resB"), snap.Resources[0].URN)
	assert.Equal(t, resource.URN(""), snap.Resources[0].Parent)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[2].URN)
	// Even though we didn't register C its URN must update to take the re-parenting into account.
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typB$pkgA:m:typC::resC"), snap.Resources[3].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typB::resB"), snap.Resources[3].Parent)

	mode = 2
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "2")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 3)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typB::resB"), snap.Resources[0].URN)
	assert.Equal(t, resource.URN(""), snap.Resources[0].Parent)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typB$pkgA:m:typC::resC"), snap.Resources[2].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typB::resB"), snap.Resources[2].Parent)
}

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

	// This is a test for https://github.com/pulumi/pulumi/issues/14041 to check that we don't courrupt state files when
	// old aliased resources are left in state because they can't delete.
	//
	// Imagine the following update flow from an old engine version where we saved aliases to state:
	//
	// 1) Creates "resA".
	//
	// 2) Makes "resAX" with an alias to "resA" so end up with one resource in the state file "resAX", but it records
	// it's alias as "resA".
	//
	// 3) Tries to destroy "resA" but fails so leaves it in the state file as is, this step doesn't actually seem
	// important for triggering the error.
	//
	// 4) Creates "resA" and again tries to delete "resAX" but again fails, now we try and save the snapshot but before
	// we save the snapshot we do URN normalisation - That looks at all the resources and sees which ones we're aliased
	// by looking at the Aliases field on their state - "resAX" still has it's alias recorded from Update 2 so it
	// registers a URN fixup of "resA" -> "resAX" - We then rewrite all the resources with that rule which means any new
	// resources created in the update (new resources are always first in the snapshot list) that were either called
	// resA or tried to use resA as a parent now all rewrite their pointers and URNs to "resAX" which is at the end of
	// the snapshot list (this explains why we we're seeing child before parent before 3.83, and now see duplicate
	// resource) - Voila bad snapshot.
	//
	// To prevent the above the engine now doesn't save Aliases in state anymore, so when we run update 4 above it
	// doesn't see any aliases saved for "resAX" and so doesn't do the broken normalisation.

	mode := 0
	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
					// We should only create things in the first and last pass
					ok := mode == 0 || mode == 2
					assert.True(t, ok, "%s tried to create but should be aliased", req.URN)

					return plugin.CreateResponse{
						ID:         "created-id",
						Properties: req.Properties,
						Status:     resource.StatusOK,
					}, nil
				},
				DeleteF: func(_ context.Context, req plugin.DeleteRequest) (plugin.DeleteResponse, error) {
					// We should only delete things in the last pass
					ok := mode == 2
					assert.True(t, ok, "%s tried to delete but should be aliased", req.URN)

					return plugin.DeleteResponse{Status: resource.StatusUnknown}, errors.New("can't delete")
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		switch mode {
		case 0:
			fallthrough
		case 2:
			// Default case, and also the last case make "resA"
			_, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resA",
				true,
				deploytest.ResourceOptions{})
			assert.NoError(t, err)

		case 1:
			// Rename "resA" to "resAX" with an alias
			_, err := monitor.RegisterResource(
				"pkgA:m:typA",
				"resAX",
				true,
				deploytest.ResourceOptions{
					Aliases: []*pulumirpc.Alias{
						makeSpecAlias("resA", "", "", ""),
					},
				})
			assert.NoError(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{T: t, HostF: hostF},
	}

	project := p.GetProject()

	// Run an update for initial state with "resA"
	snap, err := TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "1")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 2)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[1].URN)

	// Run the next case, resA should be aliased
	mode = 1
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "2")
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 2)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resAX"), snap.Resources[1].URN)

	// Run the last case, resAX should try to delete and resA should be created. We can't possibly know that resA ==
	// resAX at this point because we're not being sent aliases.
	mode = 2
	snap, err = TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "3")
	assert.Error(t, err)
	assert.NotNil(t, snap)
	assert.Nil(t, snap.VerifyIntegrity())
	assert.Len(t, snap.Resources, 3)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"), snap.Resources[1].URN)
	assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resAX"), snap.Resources[2].URN)
}