package sfcrmappservice

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"slices"
	"sort"
	"strings"
	"testing"
	"time"

	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	pagelayoutsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/pagelayoutresolutionv1"
)

func testPageLayoutResolver(
	t *testing.T,
	view catalogsemantic.AppMetadataViewV3,
) RuntimeResolvePageLayoutFunc {
	t.Helper()
	return func(
		_ context.Context,
		_ string,
		request pagelayoutsemantic.Request,
	) (pagelayoutsemantic.Response, error) {
		layout, err := testPageLayoutFromView(view, request.ObjectID)
		if err != nil {
			return pagelayoutsemantic.Response{}, err
		}
		document, err := json.Marshal(layout)
		if err != nil {
			t.Fatal(err)
		}
		selections := []pagelayoutsemantic.Selection{{LayoutID: layout.ID}}
		if len(request.RecordTypeIDs) != 0 {
			selections = make([]pagelayoutsemantic.Selection, 0, len(request.RecordTypeIDs))
			for index := range request.RecordTypeIDs {
				recordTypeID := request.RecordTypeIDs[index]
				selections = append(selections, pagelayoutsemantic.Selection{
					RecordTypeID: &recordTypeID, LayoutID: layout.ID,
				})
			}
		}
		receipts := view.Receipts
		receipts.CapabilityVersion = pagelayoutsemantic.CapabilityName
		return pagelayoutsemantic.Response{
			ObjectID: request.ObjectID, Selections: selections,
			Layouts: []pagelayoutsemantic.ResolvedLayout{{
				LayoutID: layout.ID, DocumentKind: pagelayoutsemantic.PageLayoutDocumentKind,
				DocumentFormatVersion: pagelayoutsemantic.PageLayoutDocumentFormatVersion,
				LayoutDocument:        document,
			}},
			Receipts: receipts,
		}, nil
	}
}

func testPageLayoutFromView(
	view catalogsemantic.AppMetadataViewV3,
	objectID string,
) (appPageLayout, error) {
	businessKey := objectID + "/default"
	var behavior []byte
	for _, component := range view.AppComponents {
		if component.ComponentKind == appPageLayoutComponentKind &&
			strings.EqualFold(component.BusinessKey, businessKey) {
			behavior = component.CanonicalBehavior
		}
	}
	for _, component := range view.EffectiveComponents {
		if component.ComponentKind == catalogsemantic.EffectivePageLayoutComponentKind &&
			strings.EqualFold(component.BusinessKey, businessKey) {
			behavior = component.CanonicalBehavior
		}
	}
	if len(behavior) == 0 {
		return appPageLayout{}, &permissionsemantic.Failure{Category: permissionsemantic.FailureMissing}
	}
	layout, err := decodeAppPageLayout(behavior)
	if err != nil || !layout.IsDefault || !strings.EqualFold(layout.ObjectAPIName, objectID) {
		return appPageLayout{}, unavailableSearch()
	}
	return layout, nil
}

func TestObjectDescribePermissionSelectionIncludesOnlySemanticNeighbors(t *testing.T) {
	standardObject := "standard_object"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", TableType: &standardObject,
			Fields: []searchFieldSpec{
				{APIName: "OwnerId", ReferenceTo: []string{"User"}},
				{APIName: "Region__c", PicklistValueSources: []searchFieldValueSource{{
					Object: "RegionCatalog", Field: "Region__c",
				}}},
			},
		},
		"user":          {APIName: "User", TableType: &standardObject},
		"regioncatalog": {APIName: "RegionCatalog", TableType: &standardObject},
		"contact": {
			APIName: "Contact", TableType: &standardObject,
			Fields: []searchFieldSpec{{
				APIName: "AccountId", ReferenceTo: []string{"Account"},
				ChildRelationshipName: "Contacts",
			}},
		},
		"opportunity": {
			APIName: "Opportunity", TableType: &standardObject,
			Fields: []searchFieldSpec{{APIName: "AccountId", ReferenceTo: []string{"Account"}}},
		},
		"case": {APIName: "Case", TableType: &standardObject},
	}
	selection, err := objectDescribePermissionSelection(specs["account"], specs)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(selection.Sections, []permissionsemantic.Section{
		permissionsemantic.SectionObjectPermissions,
		permissionsemantic.SectionFieldPermissions,
		permissionsemantic.SectionRecordTypePermissions,
	}) || !slices.Equal(selection.ResourceTypes, []string{"Account", "Contact", "RegionCatalog", "User"}) {
		t.Fatalf("selection=%#v", selection)
	}
}

func TestDescribeObjectProjectionCarriesOnlyReadablePathField(t *testing.T) {
	var spec searchObjectSpec
	if err := json.Unmarshal([]byte(`{
		"apiName":"Opportunity",
		"label":"Opportunity",
		"pluralLabel":"Opportunities",
		"tableType":"standard_object",
		"pathField":"StageName",
		"fields":[{
			"apiName":"StageName",
			"label":"Stage",
			"type":"picklist",
			"createable":true,
			"updateable":true
		}]
	}`), &spec); err != nil {
		t.Fatal(err)
	}
	response := describeObjectProjection(
		"v63.0",
		spec,
		permissionsemantic.ObjectPermission{
			ResourceType: "Opportunity", CanRead: true, CanCreate: true, CanEdit: true,
		},
		permissionsemantic.Response{FieldPermissions: []permissionsemantic.FieldPermission{{
			ResourceType: "Opportunity", FieldName: "StageName", CanRead: true, CanEdit: true, CanCreate: true,
		}}},
		recordTypeProjection{},
		spec.ListFields,
		nil,
	)
	if got, ok := response["pathField"]; !ok || got != "StageName" {
		t.Fatalf("describe pathField = %#v, present=%v; want StageName", got, ok)
	}

	response = describeObjectProjection(
		"v63.0",
		spec,
		permissionsemantic.ObjectPermission{ResourceType: "Opportunity", CanRead: true},
		permissionsemantic.Response{FieldPermissions: []permissionsemantic.FieldPermission{{
			ResourceType: "Opportunity", FieldName: "StageName", CanRead: false,
		}}},
		recordTypeProjection{},
		spec.ListFields,
		nil,
	)
	if got, ok := response["pathField"]; ok {
		t.Fatalf("describe exposed unreadable pathField = %#v", got)
	}
}

func TestDescribeObjectProjectionCarriesOnlyReadableRelationshipDisplayNamePath(t *testing.T) {
	spec := searchObjectSpec{
		APIName: "Widget", Label: "Widget", DisplayNameField: "Account.ExternalId__c",
		PresentationFactsV5: true,
		Fields: []searchFieldSpec{{
			APIName: "AccountId", Label: "Account", Type: "reference",
			ReferenceTo: []string{"Account"}, RelationshipName: "Account",
		}},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Widget", CanRead: true},
			{ResourceType: "Account", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Widget", FieldName: "AccountId", CanRead: true},
			{ResourceType: "Account", FieldName: "ExternalId__c", CanRead: true},
		},
	}
	response := describeObjectProjection(
		"v63.0", spec, permissions.ObjectPermissions[0], permissions,
		recordTypeProjection{}, spec.ListFields, nil,
	)
	if got := response["displayNameField"]; got != "Account.ExternalId__c" {
		t.Fatalf("readable displayNameField = %#v", got)
	}
	permissions.FieldPermissions[1].CanRead = false
	response = describeObjectProjection(
		"v63.0", spec, permissions.ObjectPermissions[0], permissions,
		recordTypeProjection{}, spec.ListFields, nil,
	)
	if got, exposed := response["displayNameField"]; exposed {
		t.Fatalf("unreadable child displayNameField exposed as %#v", got)
	}
}

func TestDescribeObjectProjectionCarriesOnlyReadableListFields(t *testing.T) {
	var spec searchObjectSpec
	if err := json.Unmarshal([]byte(`{
		"apiName":"Month_End_ARR__c",
		"label":"Month End ARR",
		"listFields":["Product_Family__c","Revenue_Month__c","Owner_Name__c"],
		"fields":[
			{"apiName":"Product_Family__c","label":"Product Family","type":"string"},
			{"apiName":"Revenue_Month__c","label":"Revenue Month","type":"date"},
			{"apiName":"Owner_Name__c","label":"Owner","type":"string"}
		]
	}`), &spec); err != nil {
		t.Fatal(err)
	}
	response := describeObjectProjection(
		"v63.0",
		spec,
		permissionsemantic.ObjectPermission{ResourceType: spec.APIName, CanRead: true},
		permissionsemantic.Response{FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: spec.APIName, FieldName: "Product_Family__c", CanRead: true},
			{ResourceType: spec.APIName, FieldName: "Revenue_Month__c", CanRead: false},
			{ResourceType: spec.APIName, FieldName: "Owner_Name__c", CanRead: true},
		}},
		recordTypeProjection{},
		spec.ListFields,
		nil,
	)
	listFields, ok := response["listFields"].([]string)
	if !ok || !slices.Equal(listFields, []string{"Product_Family__c", "Owner_Name__c"}) {
		t.Fatalf("describe listFields = %#v, present=%v", response["listFields"], ok)
	}
}

func installTestPageLayout(
	t *testing.T,
	service *Service,
	view catalogsemantic.AppMetadataViewV3,
	behavior []byte,
	override []byte,
) catalogsemantic.AppMetadataViewV3 {
	t.Helper()
	if len(behavior) != 0 {
		view.AppComponents = append(view.AppComponents, flowActionsTestComponent(
			t, appPageLayoutComponentKind, "Lead/default", string(behavior),
		))
	}
	if len(override) != 0 {
		view.EffectiveComponents = append(view.EffectiveComponents, catalogsemantic.EffectiveMetadataComponent{
			ComponentKind:          catalogsemantic.EffectivePageLayoutComponentKind,
			ComponentFormatVersion: 1,
			BusinessKey:            "Lead/default",
			ContentDigest:          catalogsemantic.ComputeEffectiveMetadataComponentDigest(override),
			CanonicalBehavior:      override,
		})
		sort.Slice(view.EffectiveComponents, func(left, right int) bool {
			leftKey := view.EffectiveComponents[left].ComponentKind + "\x00" + view.EffectiveComponents[left].BusinessKey
			rightKey := view.EffectiveComponents[right].ComponentKind + "\x00" + view.EffectiveComponents[right].BusinessKey
			return leftKey < rightKey
		})
	}
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
	if err := catalogsemantic.ValidateAppMetadataViewV3(view); err != nil {
		t.Fatalf("layout test view is invalid: %v", err)
	}
	service.appMetadataV3 = func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
		return view, nil
	}
	service.resolvePageLayoutRuntime = testPageLayoutResolver(t, view)
	service.readObjectListFieldsRuntime = testObjectListFieldsReader(t, view)
	return view
}

func describeTestService(
	t *testing.T,
	permissions permissionsemantic.Response,
) (*Service, catalogsemantic.AppMetadataViewV3) {
	t.Helper()
	searchable := true
	standardObject := "standard_object"
	systemField := true
	appField := false
	view := searchTestView(t, []searchObjectSpec{{
		APIName: "Lead", Label: "Lead", PluralLabel: "Leads", KeyPrefix: "00Q", TableType: &standardObject, Searchable: &searchable,
		ListFields: []string{"Name", "OwnerId", "Status"},
		Fields: []searchFieldSpec{
			{APIName: "Id", Label: "Lead ID", Type: "string", IsSystem: &systemField},
			{APIName: "Name", Label: "Name", Type: "string", IsNameField: true, IsSystem: &appField, Required: true, Createable: true, Updateable: true},
			{APIName: "OwnerId", Label: "Owner", Type: "reference", IsSystem: &appField, ReferenceTo: []string{"User"}},
			{APIName: "Status", Label: "Status", Type: "picklist", Createable: true, Updateable: true,
				IsSystem: &appField,
				Options:  []searchFieldOption{{Value: "Open", Label: "Open"}}},
		},
	}})
	permissions.Receipts = view.Receipts
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	return &Service{
		readObjectListFieldsRuntime: testObjectListFieldsReader(t, view),
		appMetadataV3: func(
			context.Context, catalogsemantic.GetRequest,
		) (catalogsemantic.AppMetadataViewV3, error) {
			return view, nil
		},
		permissionMetadata: &RuntimePermissionMetadataClient{readEffective: func(
			_ context.Context, _ string, request permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			if request.MinCatalogRevision == nil || *request.MinCatalogRevision != view.Receipts.CatalogRevision {
				t.Fatal("describe did not bind permissions to the AppMetadata revision")
			}
			return selectedPermissionTestResponse(permissions, request), nil
		}},
	}, view
}

func TestObjectDescribeUsesOneImmutableViewAndEffectivePermissions(t *testing.T) {
	service, _ := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Lead", CanRead: true, CanCreate: true, CanEdit: true,
		}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Lead", FieldName: "Id", CanRead: true},
			{ResourceType: "Lead", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "Lead", FieldName: "OwnerId", CanRead: false, CanEdit: true, CanCreate: true},
			{ResourceType: "Lead", FieldName: "Status", CanRead: true, CanEdit: true, CanCreate: true},
		},
	})
	originalRead := service.permissionMetadata.readEffective
	var permissionRequest permissionsemantic.Request
	service.permissionMetadata.readEffective = func(
		ctx context.Context, bearer string, request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		permissionRequest = request
		return originalRead(ctx, bearer, request)
	}
	result, err := service.readObjectDescribe(context.Background(), "bearer", "v63.0", "Lead")
	if err != nil {
		t.Fatal(err)
	}
	if permissionRequest.Selection == nil || !slices.Equal(permissionRequest.Selection.Sections, []permissionsemantic.Section{
		permissionsemantic.SectionObjectPermissions,
		permissionsemantic.SectionFieldPermissions,
		permissionsemantic.SectionRecordTypePermissions,
	}) || !slices.Equal(permissionRequest.Selection.ResourceTypes, []string{"Lead"}) {
		t.Fatalf("describe permission selection = %#v", permissionRequest.Selection)
	}
	fields, ok := result["fields"].([]map[string]any)
	if !ok || len(fields) != 3 {
		t.Fatalf("fields = %#v", result["fields"])
	}
	if fields[0]["name"] != "Id" || fields[0]["createable"] != false ||
		fields[0]["referenceTo"] == nil || len(fields[0]["referenceTo"].([]string)) != 0 ||
		fields[1]["name"] != "Name" || fields[1]["nameField"] != true ||
		fields[1]["createable"] != true || fields[1]["nillable"] != false ||
		fields[2]["name"] != "Status" || len(fields[2]["picklistValues"].([]any)) != 1 ||
		result["labelPlural"] != "Leads" || result["retrieveable"] != true ||
		result["replicateable"] != true || result["triggerable"] != true ||
		result["mergeable"] != false || result["undeletable"] != true ||
		result["deprecatedAndHidden"] != false || result["feedEnabled"] != true {
		t.Fatalf("unexpected describe fields: %#v", fields)
	}
	if listFields, ok := result["listFields"].([]string); !ok ||
		!slices.Equal(listFields, []string{"Name", "Status"}) {
		t.Fatalf("describe list fields = %#v, want readable configured fields", result["listFields"])
	}
	urls, ok := result["urls"].(map[string]string)
	if !ok || urls["describe"] != "/services/data/v63.0/sobjects/Lead/describe" ||
		urls["rowTemplate"] != "/services/data/v63.0/sobjects/Lead/{ID}" {
		t.Fatalf("unexpected describe urls: %#v", result["urls"])
	}
	recordTypes := result["recordTypeInfos"].([]map[string]any)
	if len(recordTypes) != 1 || recordTypes[0]["recordTypeId"] != masterRecordTypeID ||
		recordTypes[0]["master"] != true || recordTypes[0]["defaultRecordTypeMapping"] != true {
		t.Fatalf("Lead record types = %#v, want Master fallback", recordTypes)
	}
	childRelationships, ok := result["childRelationships"].([]map[string]any)
	if !ok || len(childRelationships) != 0 {
		t.Fatalf("Lead child relationships = %#v, want an empty array", result["childRelationships"])
	}
}

func TestObjectDescribeUsesCurrentRuntimeListFields(t *testing.T) {
	service, view := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Lead", FieldName: "Name", CanRead: true},
			{ResourceType: "Lead", FieldName: "Status", CanRead: true},
		},
	})
	service.readObjectListFieldsRuntime = func(
		_ context.Context, _ string, request pagelayoutsemantic.ObjectListFieldsRequest,
	) (pagelayoutsemantic.ObjectListFieldsResponse, error) {
		receipts := view.Receipts
		receipts.CapabilityVersion = pagelayoutsemantic.CapabilityName
		return pagelayoutsemantic.ObjectListFieldsResponse{
			Objects: []pagelayoutsemantic.ObjectListFields{{
				ObjectID: request.ObjectIDs[0], OrderedFieldIDs: []string{"Status", "Name"},
			}},
			Receipts: receipts,
		}, nil
	}
	result, err := service.readObjectDescribe(context.Background(), "bearer", "v63.0", "Lead")
	if err != nil {
		t.Fatal(err)
	}
	if fields, ok := result["listFields"].([]string); !ok ||
		!slices.Equal(fields, []string{"Status", "Name"}) {
		t.Fatalf("describe list fields = %#v, want Runtime order", result["listFields"])
	}
}

func TestDescribeChildRelationshipsUsesEffectiveObjectAndFieldAuthority(t *testing.T) {
	standardObject := "standard_object"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", Label: "Account", TableType: &standardObject,
		},
		"contact": {
			APIName: "Contact", Label: "Contact", TableType: &standardObject,
			Fields: []searchFieldSpec{
				{
					APIName: "AccountId", Type: "reference",
					ReferenceTo: []string{"Account"}, ChildRelationshipName: "Contacts",
					IsMasterDetail: true,
				},
				{
					APIName: "PrivateAccountId", Type: "reference",
					ReferenceTo: []string{"Account"}, ChildRelationshipName: "PrivateContacts",
				},
			},
		},
		"opportunity": {
			APIName: "Opportunity", Label: "Opportunity", TableType: &standardObject,
			Fields: []searchFieldSpec{{
				APIName: "AccountId", Type: "reference", ReferenceTo: []string{"Account"},
				ChildRelationshipName: "Opportunities", DeleteRule: "SetNull",
			}},
		},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true},
			{ResourceType: "Contact", CanRead: true},
			{ResourceType: "Opportunity", CanRead: false},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Contact", FieldName: "AccountId", CanRead: true},
			{ResourceType: "Contact", FieldName: "PrivateAccountId", CanRead: false},
			{ResourceType: "Opportunity", FieldName: "AccountId", CanRead: true},
		},
	}
	relationships, err := describeChildRelationships(specs["account"], specs, permissions)
	if err != nil {
		t.Fatal(err)
	}
	if len(relationships) != 1 || relationships[0]["relationshipName"] != "Contacts" ||
		relationships[0]["childSObject"] != "Contact" ||
		relationships[0]["field"] != "AccountId" ||
		relationships[0]["cascadeDelete"] != true ||
		relationships[0]["restrictedDelete"] != false {
		t.Fatalf("authorized child relationships = %#v", relationships)
	}
}

func TestDescribeChildRelationshipsFailsClosedOnDuplicateEffectiveIdentity(t *testing.T) {
	standardObject := "standard_object"
	specs := map[string]searchObjectSpec{
		"account": {APIName: "Account", TableType: &standardObject},
		"contact": {
			APIName: "Contact", TableType: &standardObject,
			Fields: []searchFieldSpec{
				{APIName: "AccountId", ReferenceTo: []string{"Account"}, ChildRelationshipName: "Contacts"},
				{APIName: "OtherAccountId", ReferenceTo: []string{"Account"}, ChildRelationshipName: "contacts"},
			},
		},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true}, {ResourceType: "Contact", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Contact", FieldName: "AccountId", CanRead: true},
			{ResourceType: "Contact", FieldName: "OtherAccountId", CanRead: true},
		},
	}
	if _, err := describeChildRelationships(specs["account"], specs, permissions); err == nil {
		t.Fatal("duplicate effective child relationship identity was accepted")
	}
}

func TestObjectDescribeServesWithoutRuntimeSearch(t *testing.T) {
	service, _ := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
		FieldPermissions:  []permissionsemantic.FieldPermission{{ResourceType: "Lead", FieldName: "Id", CanRead: true}},
	})
	if service.search != nil {
		t.Fatal("test composition unexpectedly installed Runtime Search")
	}
	request := httptest.NewRequest(http.MethodGet, "/services/data/v63.0/sobjects/Lead/describe", nil)
	request.Header.Set("Authorization", "Bearer token")
	response := httptest.NewRecorder()
	if !service.serveObjectDescribe(response, request) || response.Code != http.StatusOK {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestGlobalObjectDescribeUsesSameRevisionBoundAuthority(t *testing.T) {
	service, _ := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Lead", CanRead: true, CanCreate: true, CanEdit: true,
		}},
	})
	read := service.permissionMetadata.readEffective
	var permissionRequests []permissionsemantic.Request
	service.permissionMetadata.readEffective = func(
		ctx context.Context,
		bearer string,
		request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		permissionRequests = append(permissionRequests, request)
		return read(ctx, bearer, request)
	}
	request := httptest.NewRequest(http.MethodGet, "/services/data/v63.0/sobjects", nil)
	request.Header.Set("Authorization", "Bearer token")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
	var body struct {
		Encoding     string           `json:"encoding"`
		MaxBatchSize int              `json:"maxBatchSize"`
		SObjects     []map[string]any `json:"sobjects"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body.Encoding != "UTF-8" || body.MaxBatchSize != 200 ||
		len(body.SObjects) != 2 || body.SObjects[0]["name"] != "Lead" ||
		body.SObjects[1]["name"] != "LeadHistory" ||
		body.SObjects[0]["createable"] != true ||
		body.SObjects[0]["updateable"] != true {
		t.Fatalf("global describe = %#v", body)
	}
	urls, ok := body.SObjects[0]["urls"].(map[string]any)
	if !ok || urls["describe"] != "/services/data/v63.0/sobjects/Lead/describe" {
		t.Fatalf("global describe urls = %#v", body.SObjects[0]["urls"])
	}
	if len(permissionRequests) != 1 || permissionRequests[0].Selection == nil ||
		!slices.Equal(permissionRequests[0].Selection.Sections, []permissionsemantic.Section{
			permissionsemantic.SectionObjectPermissions,
		}) || !slices.Equal(permissionRequests[0].Selection.ResourceTypes, []string{"Lead"}) {
		t.Fatalf("global describe permission requests = %#v", permissionRequests)
	}
}

func TestGlobalObjectDescribeOmitsUnreadableObjects(t *testing.T) {
	service, _ := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Lead", CanRead: false,
		}},
	})
	response, err := service.readGlobalDescribe(context.Background(), "bearer", "v63.0")
	if err != nil {
		t.Fatal(err)
	}
	objects, ok := response["sobjects"].([]map[string]any)
	if !ok || len(objects) != 0 {
		t.Fatalf("unreadable global objects = %#v", response["sobjects"])
	}
}

func TestGlobalObjectDescribeDoesNotDiscloseUnreadableSharingParent(t *testing.T) {
	spec := searchObjectSpec{
		APIName: "Child", Label: "Child", PresentationFactsV5: true,
		SharingModel: "ControlledByParent", SharingParentObject: "Parent",
	}
	child := permissionsemantic.ObjectPermission{ResourceType: "Child", CanRead: true}
	permissions := permissionsemantic.Response{ObjectPermissions: []permissionsemantic.ObjectPermission{child}}
	projected := describeGlobalObjectProjection("v63.0", spec, child, permissions)
	if parent, present := projected["sharingParentObject"]; present {
		t.Fatalf("unreadable sharing parent = %#v, want omitted", parent)
	}
	permissions.ObjectPermissions = append(permissions.ObjectPermissions,
		permissionsemantic.ObjectPermission{ResourceType: "Parent", CanRead: true},
	)
	projected = describeGlobalObjectProjection("v63.0", spec, child, permissions)
	if parent := projected["sharingParentObject"]; parent != "Parent" {
		t.Fatalf("authorized sharing parent = %#v, want Parent", parent)
	}
}

func TestHistoryDescribeDerivesFromParentCatalogAndPermissionAuthority(t *testing.T) {
	searchable := true
	standardObject := "standard_object"
	systemField := true
	appField := false
	view := searchTestView(t, []searchObjectSpec{{
		APIName: "Account", Label: "Account", PluralLabel: "Accounts", KeyPrefix: "001",
		TableType: &standardObject, Searchable: &searchable,
		Fields: []searchFieldSpec{
			{APIName: "Name", Label: "Account Name", Type: "string", IsNameField: true, IsSystem: &appField},
			{APIName: "Id", Label: "Account ID", Type: "string", IsSystem: &systemField},
			{APIName: "Phone", Label: "Phone", Type: "string", IsSystem: &appField},
		},
	}})
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Account", CanRead: true, CanCreate: true, CanEdit: true,
		}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Id", CanRead: true},
			{ResourceType: "Account", FieldName: "Name", CanRead: true},
			{ResourceType: "Account", FieldName: "Phone", CanRead: false},
		},
		Receipts: view.Receipts,
	}
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	service := &Service{
		appMetadataV3: func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
			return view, nil
		},
		permissionMetadata: &RuntimePermissionMetadataClient{readEffective: func(
			_ context.Context, _ string, request permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			if request.MinCatalogRevision == nil || *request.MinCatalogRevision != view.Receipts.CatalogRevision {
				t.Fatal("history describe did not bind permissions to the parent Catalog revision")
			}
			return selectedPermissionTestResponse(permissions, request), nil
		}},
	}

	global, err := service.readGlobalDescribe(context.Background(), "bearer", "v63.0")
	if err != nil {
		t.Fatal(err)
	}
	objects := global["sobjects"].([]map[string]any)
	if len(objects) != 2 || objects[0]["name"] != "Account" || objects[1]["name"] != "AccountHistory" {
		t.Fatalf("global objects = %#v", objects)
	}
	if objects[1]["createable"] != false || objects[1]["queryable"] != true || objects[1]["isSystem"] != true {
		t.Fatalf("AccountHistory global describe = %#v", objects[1])
	}

	describe, err := service.readObjectDescribe(context.Background(), "bearer", "v63.0", "AccountHistory")
	if err != nil {
		t.Fatal(err)
	}
	if describe["name"] != "AccountHistory" || describe["createable"] != false || describe["updateable"] != false {
		t.Fatalf("AccountHistory describe = %#v", describe)
	}
	fields := describe["fields"].([]map[string]any)
	if len(fields) != 8 || fields[1]["name"] != "AccountId" || fields[2]["name"] != "Field" {
		t.Fatalf("AccountHistory fields = %#v", fields)
	}
	picklist := fields[2]["picklistValues"].([]map[string]any)
	if len(picklist) != 2 || picklist[0]["value"] != "Name" || picklist[1]["value"] != "Id" {
		t.Fatalf("AccountHistory readable field values = %#v", picklist)
	}
}

func TestObjectLayoutsUsesEffectiveOverrideAndFieldPermissions(t *testing.T) {
	service, view := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Lead", CanRead: true, CanCreate: true, CanEdit: true,
		}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Lead", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "Lead", FieldName: "Status", CanRead: true, CanEdit: false, CanCreate: false},
		},
	})
	base := []byte(`{"objectApiName":"Lead","layoutName":"Base","isDefault":true,"compactLayout":[],"sections":[{"id":"base","label":"Base","columns":1,"fields":["Name"]}],"relatedLists":[]}`)
	override := []byte(`{"objectApiName":"Lead","layoutName":"Effective","isDefault":true,"compactLayout":[],"sections":[{"id":"details","label":"Details","columns":2,"fields":["Name","Status","OwnerId"]}],"relatedLists":[]}`)
	view = installTestPageLayout(t, service, view, base, override)
	service.permissionMetadata.readEffective = func(
		_ context.Context, _ string, request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		if request.MinCatalogRevision == nil || *request.MinCatalogRevision != view.Receipts.CatalogRevision {
			t.Fatal("layouts did not bind permissions to AppMetadata revision")
		}
		if request.Selection == nil || len(request.Selection.Sections) != 3 ||
			request.Selection.Sections[0] != permissionsemantic.SectionObjectPermissions ||
			request.Selection.Sections[1] != permissionsemantic.SectionFieldPermissions ||
			request.Selection.Sections[2] != permissionsemantic.SectionRecordTypePermissions ||
			len(request.Selection.ResourceTypes) != 1 || request.Selection.ResourceTypes[0] != "Lead" {
			t.Fatalf("layout permission selection = %#v", request.Selection)
		}
		response := permissionsemantic.Response{
			ObjectPermissions: []permissionsemantic.ObjectPermission{{
				ResourceType: "Lead", CanRead: true, CanCreate: true, CanEdit: true,
			}},
			FieldPermissions: []permissionsemantic.FieldPermission{
				{ResourceType: "Lead", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
				{ResourceType: "Lead", FieldName: "Status", CanRead: true, CanEdit: false, CanCreate: false},
			},
			Receipts: view.Receipts,
		}
		response.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
		return response, nil
	}
	response, err := service.readObjectLayouts(context.Background(), "bearer", objectLayoutsRoute{
		version: "v63.0", objectID: "Lead",
	})
	if err != nil {
		t.Fatal(err)
	}
	layouts := response["layouts"].([]map[string]any)
	if len(layouts) != 1 || layouts[0]["name"] != "Effective" {
		t.Fatalf("layouts = %#v", layouts)
	}
	sections := layouts[0]["editLayoutSections"].([]map[string]any)
	items := sections[0]["layoutRows"].([]map[string]any)[0]["layoutItems"].([]map[string]any)
	if len(items) != 2 || items[0]["field"] != "Name" || items[0]["editableForUpdate"] != true ||
		items[1]["field"] != "Status" || items[1]["editableForUpdate"] != false {
		t.Fatalf("layout items = %#v", items)
	}
	mappings := response["recordTypeMappings"].([]map[string]any)
	if len(mappings) != 1 || mappings[0]["recordTypeId"] != masterRecordTypeID {
		t.Fatalf("record type mappings = %#v", mappings)
	}
}

func TestObjectDescribeNormalizesSalesforceFieldTypesAndDefaults(t *testing.T) {
	zero := "'0'"
	count := "42"
	currencyPrecision := 18
	currencyScale := 2
	spec := searchObjectSpec{APIName: "Metric__c", Label: "Metric", Fields: []searchFieldSpec{
		{APIName: "Id", Label: "ID", Type: "string"},
		{APIName: "Enabled__c", Label: "Enabled", Type: "boolean", DefaultValue: &zero, Createable: true, Updateable: true},
		{APIName: "Count__c", Label: "Count", Type: "rollupsummary", ReturnType: "integer", DefaultValue: &count},
		{APIName: "Revenue__c", Label: "Revenue", Type: "rollupsummary", ReturnType: "currency", Precision: &currencyPrecision, Scale: &currencyScale},
		{APIName: "Sequence__c", Label: "Sequence", Type: "autonumber"},
		{APIName: "Notes__c", Label: "Notes", Type: "richtextarea"},
	}}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: spec.APIName, CanRead: true, CanCreate: true, CanEdit: true}},
	}
	for _, field := range spec.Fields {
		permissions.FieldPermissions = append(permissions.FieldPermissions, permissionsemantic.FieldPermission{
			ResourceType: spec.APIName, FieldName: field.APIName, CanRead: true, CanEdit: true, CanCreate: true,
		})
	}
	result := describeObjectProjection("v63.0", spec, permissions.ObjectPermissions[0], permissions, testMasterRecordTypeProjection(), spec.ListFields, nil)
	fields := result["fields"].([]map[string]any)
	if fields[0]["type"] != "id" || fields[1]["type"] != "boolean" || fields[1]["nillable"] != false ||
		fields[1]["defaultValue"] != false || fields[1]["defaultedOnCreate"] != true ||
		fields[2]["type"] != "int" || fields[2]["defaultValue"] != float64(42) ||
		fields[2]["precision"] != 18 || fields[2]["scale"] != 0 ||
		fields[3]["type"] != "currency" || fields[3]["precision"] != 18 ||
		fields[3]["scale"] != 2 ||
		fields[4]["type"] != "string" || fields[4]["autoNumber"] != true ||
		fields[5]["type"] != "textarea" || fields[5]["htmlFormatted"] != true {
		t.Fatalf("normalized fields = %#v", fields)
	}
}

func TestObjectDescribeConsumesCatalogDefaultValueWireKey(t *testing.T) {
	behavior := []byte(`{"objects":[{"apiName":"Example","label":"Example","fields":[{"apiName":"Enabled__c","label":"Enabled","type":"boolean","createable":true,"updateable":true,"defaultValue":"1"}]}]}`)
	specs, err := searchObjectSpecsFromView(catalogsemantic.AppMetadataViewV3{
		EffectiveComponents: []catalogsemantic.EffectiveMetadataComponent{{
			ComponentKind:          catalogsemantic.EffectiveObjectSearchComponentKind,
			ComponentFormatVersion: effectiveObjectSearchComponentFormatV3,
			BusinessKey:            catalogsemantic.EffectiveObjectSearchComponentBusinessKey,
			CanonicalBehavior:      behavior,
		}},
	})
	if err != nil {
		t.Fatal(err)
	}
	spec := specs["example"]
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: spec.APIName, CanRead: true, CanCreate: true, CanEdit: true,
		}},
		FieldPermissions: []permissionsemantic.FieldPermission{{
			ResourceType: spec.APIName, FieldName: "Enabled__c", CanRead: true, CanEdit: true, CanCreate: true,
		}},
	}
	result := describeObjectProjection(
		"v63.0", spec, permissions.ObjectPermissions[0], permissions,
		testMasterRecordTypeProjection(), spec.ListFields, nil,
	)
	fields := result["fields"].([]map[string]any)
	if len(fields) != 1 || fields[0]["defaultValue"] != true || fields[0]["defaultedOnCreate"] != true {
		t.Fatalf("describe field = %#v", fields)
	}
}

func TestObjectDescribeProjectsExternalIDUniquenessFromCatalog(t *testing.T) {
	spec := searchObjectSpec{APIName: "External_Key_Test__c", Label: "External Key Test", Fields: []searchFieldSpec{
		{APIName: "Id", Label: "Record ID", Type: "string"},
		{APIName: "Required__c", Label: "Required", Type: "string", Required: true, Createable: true, Updateable: true},
		{APIName: "External__c", Label: "External", Type: "string", Unique: true, Createable: true, Updateable: true},
		{APIName: "Hidden_External__c", Label: "Hidden External", Type: "string", Unique: true, Createable: true, Updateable: true},
	}}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: spec.APIName, CanRead: true, CanCreate: true, CanEdit: true,
		}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: spec.APIName, FieldName: "Id", CanRead: true},
			{ResourceType: spec.APIName, FieldName: "Required__c", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: spec.APIName, FieldName: "External__c", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: spec.APIName, FieldName: "Hidden_External__c", CanRead: false, CanEdit: true, CanCreate: true},
		},
	}
	result := describeObjectProjection(
		"v63.0", spec, permissions.ObjectPermissions[0], permissions,
		testMasterRecordTypeProjection(), spec.ListFields, nil,
	)
	fields := result["fields"].([]map[string]any)
	if len(fields) != 3 {
		t.Fatalf("visible fields = %#v", fields)
	}
	if fields[0]["name"] != "Id" || fields[0]["unique"] != false ||
		fields[0]["externalId"] != false || fields[0]["idLookup"] != true {
		t.Fatalf("Id uniqueness semantics = %#v", fields[0])
	}
	if fields[1]["name"] != "Required__c" || fields[1]["nillable"] != false ||
		fields[1]["unique"] != false || fields[1]["externalId"] != false ||
		fields[1]["idLookup"] != false {
		t.Fatalf("required non-unique semantics = %#v", fields[1])
	}
	if fields[2]["name"] != "External__c" || fields[2]["nillable"] != true ||
		fields[2]["unique"] != true || fields[2]["externalId"] != true ||
		fields[2]["idLookup"] != true {
		t.Fatalf("external-ID uniqueness semantics = %#v", fields[2])
	}
}

func TestObjectDescribeUsesCatalogClassificationForCustomSemantics(t *testing.T) {
	system := true
	tableType := "custom_object"
	spec := searchObjectSpec{
		APIName: "IdentitySyncSetting", Label: "Identity Sync Setting", TableType: &tableType,
		Fields: []searchFieldSpec{
			{APIName: "Id", Label: "ID", Type: "string", IsSystem: &system},
			// Current Catalog components omit isSystem when it is false.
			{APIName: "PolicyName", Label: "Policy Name", Type: "string"},
		},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: spec.APIName, CanRead: true}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: spec.APIName, FieldName: "Id", CanRead: true},
			{ResourceType: spec.APIName, FieldName: "PolicyName", CanRead: true},
		},
	}
	result := describeObjectProjection("v63.0", spec, permissions.ObjectPermissions[0], permissions, testMasterRecordTypeProjection(), spec.ListFields, nil)
	fields := result["fields"].([]map[string]any)
	if result["custom"] != true || fields[0]["custom"] != false || fields[1]["custom"] != true {
		t.Fatalf("custom classification object=%v fields=%#v", result["custom"], fields)
	}
	if !describeAuthorityIsCurrent(spec) {
		t.Fatal("current sparse Catalog classification was rejected")
	}
}

func TestObjectDescribePreservesCalculatedCompoundAndDependentMetadata(t *testing.T) {
	spec := searchObjectSpec{APIName: "Account", Label: "Account", Fields: []searchFieldSpec{
		{APIName: "Score__c", Label: "Score", Type: "formula", ReturnType: "number"},
		{APIName: "BillingAddress", Label: "Billing Address", Type: "address"},
		{APIName: "BillingCity", Label: "Billing City", Type: "string", CompoundFieldName: "BillingAddress"},
		{APIName: "Country__c", Label: "Country", Type: "picklist", Options: []searchFieldOption{
			{Value: "US", Label: "United States"}, {Value: "CN", Label: "China"},
		}},
		{APIName: "State__c", Label: "State", Type: "picklist", ControllingField: "Country__c",
			Options:            []searchFieldOption{{Value: "CA", Label: "California"}, {Value: "BJ", Label: "Beijing"}},
			PicklistDependency: map[string][]string{"US": {"CA"}, "CN": {"BJ"}}},
	}}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: spec.APIName, CanRead: true}},
	}
	for _, field := range spec.Fields {
		permissions.FieldPermissions = append(permissions.FieldPermissions, permissionsemantic.FieldPermission{
			ResourceType: spec.APIName, FieldName: spec.APIName + "." + field.APIName, CanRead: true,
		})
	}
	result := describeObjectProjection("v63.0", spec, permissions.ObjectPermissions[0], permissions, testMasterRecordTypeProjection(), spec.ListFields, nil)
	fields := result["fields"].([]map[string]any)
	if len(fields) != 5 {
		t.Fatalf("qualified FLS fields = %#v", fields)
	}
	if fields[0]["calculated"] != true || fields[0]["type"] != "double" ||
		fields[2]["compoundFieldName"] != "BillingAddress" ||
		fields[4]["controllerName"] != "Country__c" || fields[4]["dependentPicklist"] != true {
		t.Fatalf("describe metadata = %#v", fields)
	}
	dependentValues := fields[4]["picklistValues"].([]any)
	if len(dependentValues) != 2 || dependentValues[0].(map[string]any)["validFor"] != "gA==" ||
		dependentValues[1].(map[string]any)["validFor"] != "QA==" {
		t.Fatalf("dependent values = %#v", dependentValues)
	}
}

func TestObjectDescribeProjectsRuntimeScopedPicklistMetadata(t *testing.T) {
	standardObject := "standard_object"
	campaignMember := searchObjectSpec{
		APIName: "CampaignMember", Label: "Campaign Member", TableType: &standardObject,
		Fields: []searchFieldSpec{
			{APIName: "CampaignId", Label: "Campaign", Type: "reference", ReferenceTo: []string{"Campaign"}},
			{
				APIName: "Status", Label: "Status", Type: "string",
				ControllingField: "CampaignId", ReferenceTo: []string{"CampaignMemberStatus"},
				Validator: "runtime_scoped_picklist",
				Options: []searchFieldOption{
					{Value: "Sent", Label: "Sent"},
					{Value: "Responded", Label: "Responded"},
				},
			},
		},
	}
	campaignMemberStatus := searchObjectSpec{
		APIName: "CampaignMemberStatus", Label: "Campaign Member Status", TableType: &standardObject,
		Fields: []searchFieldSpec{
			{APIName: "CampaignId", Label: "Campaign", Type: "reference", ReferenceTo: []string{"Campaign"}},
			{APIName: "Label", Label: "Label", Type: "string", IsNameField: true},
			{APIName: "SortOrder", Label: "Sort Order", Type: "int"},
		},
	}
	specs := map[string]searchObjectSpec{
		"campaignmember":       campaignMember,
		"campaignmemberstatus": campaignMemberStatus,
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Campaign", CanRead: true},
			{ResourceType: campaignMember.APIName, CanRead: true},
			{ResourceType: campaignMemberStatus.APIName, CanRead: true},
		},
	}
	for _, spec := range specs {
		for _, field := range spec.Fields {
			permissions.FieldPermissions = append(permissions.FieldPermissions, permissionsemantic.FieldPermission{
				ResourceType: spec.APIName, FieldName: field.APIName, CanRead: true,
			})
		}
	}

	result := describeObjectProjectionWithSpecs(
		"v63.0", campaignMember, permissions.ObjectPermissions[1], permissions,
		testMasterRecordTypeProjection(), campaignMember.ListFields, nil, specs,
	)
	fields := result["fields"].([]map[string]any)
	status := fields[1]
	filteredLookupInfo, ok := status["filteredLookupInfo"].(map[string]any)
	if !ok {
		t.Fatalf("runtime picklist filteredLookupInfo = %#v", status["filteredLookupInfo"])
	}
	runtimePicklist, ok := filteredLookupInfo["runtimePicklist"].(map[string]any)
	if !ok || status["type"] != "picklist" ||
		status["controllerName"] != "CampaignId" || status["dependentPicklist"] != true ||
		runtimePicklist["sourceObject"] != "CampaignMemberStatus" ||
		runtimePicklist["valueField"] != "Label" ||
		runtimePicklist["labelField"] != "Label" ||
		runtimePicklist["sortField"] != "SortOrder" ||
		runtimePicklist["sortOrder"] != "ASC" {
		t.Fatalf("runtime picklist describe metadata = %#v", status)
	}
	mappings, ok := runtimePicklist["filterMappings"].([]map[string]any)
	if !ok || len(mappings) != 1 || mappings[0]["sourceField"] != "CampaignId" ||
		mappings[0]["recordField"] != "CampaignId" {
		t.Fatalf("runtime picklist filter mappings = %#v", runtimePicklist["filterMappings"])
	}

	for index := range permissions.FieldPermissions {
		permission := &permissions.FieldPermissions[index]
		if permission.ResourceType == campaignMemberStatus.APIName && permission.FieldName == "CampaignId" {
			permission.CanRead = false
		}
	}
	result = describeObjectProjectionWithSpecs(
		"v63.0", campaignMember, permissions.ObjectPermissions[1], permissions,
		testMasterRecordTypeProjection(), campaignMember.ListFields, nil, specs,
	)
	status = result["fields"].([]map[string]any)[1]
	filteredLookupInfo, ok = status["filteredLookupInfo"].(map[string]any)
	unusableRuntimePicklist, ok := filteredLookupInfo["runtimePicklist"].(map[string]any)
	if !ok || len(unusableRuntimePicklist) != 0 || status["dependentPicklist"] != true {
		t.Fatalf("runtime picklist did not fail closed for an unreadable source field: %#v", status)
	}
}

func TestRuntimeLookupDescribeFailsClosedForUnreadableMappingField(t *testing.T) {
	validatorConfig := `{"filterMappings":[{"recordField":"OpportunityId",` +
		`"sourceField":"Pricebook2Id","targetField":"Pricebook2Id"}]}`
	source := searchObjectSpec{
		APIName: "OpportunityLineItem",
		Fields: []searchFieldSpec{
			{
				APIName: "PricebookEntryId", Type: "reference",
				ReferenceTo: []string{"PricebookEntry"},
				Validator:   runtimeScopedLookupRule, ValidatorConfig: validatorConfig,
			},
			{APIName: "OpportunityId", Type: "reference", ReferenceTo: []string{"Opportunity"}},
		},
	}
	specs := map[string]searchObjectSpec{
		"opportunitylineitem": source,
		"opportunity": {
			APIName: "Opportunity",
			Fields:  []searchFieldSpec{{APIName: "Pricebook2Id", Type: "reference"}},
		},
		"pricebookentry": {
			APIName: "PricebookEntry",
			Fields:  []searchFieldSpec{{APIName: "Pricebook2Id", Type: "reference"}},
		},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "OpportunityLineItem", CanRead: true},
			{ResourceType: "Opportunity", CanRead: true},
			{ResourceType: "PricebookEntry", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "OpportunityLineItem", FieldName: "OpportunityId", CanRead: true},
			{ResourceType: "Opportunity", FieldName: "Pricebook2Id", CanRead: true},
		},
	}
	field := source.Fields[0]
	if got := describeRuntimeLookupInfo(
		field, []string{"PricebookEntry"}, source, specs, permissions,
	); len(got) != 0 {
		t.Fatalf("runtime lookup disclosed unreadable target field: %#v", got)
	}
	permissions.FieldPermissions = append(
		permissions.FieldPermissions,
		permissionsemantic.FieldPermission{
			ResourceType: "PricebookEntry", FieldName: "Pricebook2Id", CanRead: true,
		},
	)
	got := describeRuntimeLookupInfo(
		field, []string{"PricebookEntry"}, source, specs, permissions,
	)
	mappings, ok := got["filterMappings"].([]map[string]any)
	if !ok || len(mappings) != 1 || mappings[0]["targetField"] != "Pricebook2Id" {
		t.Fatalf("authorized runtime lookup metadata = %#v", got)
	}
}

func TestRuntimeLookupDescribeAcceptsImplicitSourceID(t *testing.T) {
	validatorConfig := `{"filterMappings":[{"recordField":"ParentId",` +
		`"sourceField":"Id","targetField":"ParentId"}]}`
	source := searchObjectSpec{
		APIName: "Child",
		Fields: []searchFieldSpec{
			{
				APIName: "ScopedTargetId", Type: "reference", ReferenceTo: []string{"Target"},
				Validator: runtimeScopedLookupRule, ValidatorConfig: validatorConfig,
			},
			{APIName: "ParentId", Type: "reference", ReferenceTo: []string{"Parent"}},
		},
	}
	specs := map[string]searchObjectSpec{
		"child":  source,
		"parent": {APIName: "Parent"},
		"target": {
			APIName: "Target",
			Fields:  []searchFieldSpec{{APIName: "ParentId", Type: "reference"}},
		},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Child", CanRead: true},
			{ResourceType: "Parent", CanRead: true},
			{ResourceType: "Target", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Child", FieldName: "ParentId", CanRead: true},
			{ResourceType: "Target", FieldName: "ParentId", CanRead: true},
		},
	}
	got := describeRuntimeLookupInfo(
		source.Fields[0], []string{"Target"}, source, specs, permissions,
	)
	mappings, ok := got["filterMappings"].([]map[string]any)
	if !ok || len(mappings) != 1 || mappings[0]["sourceField"] != "Id" ||
		mappings[0]["targetField"] != "ParentId" {
		t.Fatalf("implicit source Id runtime lookup metadata = %#v", got)
	}
}

func TestObjectDescribeDerivesPolymorphicMarkerFromReadableTargets(t *testing.T) {
	spec := searchObjectSpec{
		APIName: "Activity",
		Fields: []searchFieldSpec{{
			APIName: "WhoId", Label: "Name", Type: "reference",
			ReferenceTo: []string{"Contact", "Lead"}, IsPolymorphic: true,
		}},
	}
	objectPermission := permissionsemantic.ObjectPermission{ResourceType: "Activity", CanRead: true}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			objectPermission,
			{ResourceType: "Contact", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{{
			ResourceType: "Activity", FieldName: "WhoId", CanRead: true,
		}},
	}
	specs := map[string]searchObjectSpec{
		"activity": spec,
		"contact":  {APIName: "Contact"},
		"lead":     {APIName: "Lead"},
	}
	projected := describeObjectProjectionWithSpecs(
		"v63.0", spec, objectPermission, permissions, recordTypeProjection{}, spec.ListFields, nil, specs,
	)
	fields := projected["fields"].([]map[string]any)
	if len(fields) != 1 || fields[0]["polymorphicForeignKey"] != false ||
		!slices.Equal(fields[0]["referenceTo"].([]string), []string{"Contact"}) {
		t.Fatalf("permission-filtered polymorphic field = %#v", fields)
	}
}

func TestObjectDescribeReturnsFormulaBodyAndAuthorizedPicklistSourceValues(t *testing.T) {
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true},
			{ResourceType: "Opportunity", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Region__c", CanRead: true},
			{ResourceType: "Opportunity", FieldName: "AccountId", CanRead: true},
			{ResourceType: "Opportunity", FieldName: "Region__c", CanRead: true},
		},
	}
	service, view := describeTestService(t, permissions)
	behavior := json.RawMessage(`{"objects":[
		{"apiName":"Account","label":"Account","tableType":"standard_object","fields":[
			{"apiName":"Region__c","label":"Region","type":"picklist","createable":false,"updateable":false,
			 "defaultValue":"APAC",
			 "options":[{"value":"APAC","label":"Asia Pacific","tags":["regional"]}]}
		]},
		{"apiName":"Opportunity","label":"Opportunity","tableType":"standard_object","fields":[
			{"apiName":"AccountId","label":"Account","type":"reference","createable":false,"updateable":false,
			 "referenceTo":["Account"]},
			{"apiName":"Region__c","label":"Region","type":"formula","returnType":"string",
			 "createable":false,"updateable":false,"formula":"TEXT(Account.Region__c)",
			 "picklistValueSources":[{"object":"Account","field":"Region__c"}]}
		]}
	]}`)
	component := &view.EffectiveComponents[0]
	component.ComponentFormatVersion = 4
	component.CanonicalBehavior = behavior
	component.ContentDigest = catalogsemantic.ComputeEffectiveMetadataComponentDigest(behavior)
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
	service.appMetadataV3 = func(
		context.Context, catalogsemantic.GetRequest,
	) (catalogsemantic.AppMetadataViewV3, error) {
		return view, nil
	}
	service.readObjectListFieldsRuntime = testObjectListFieldsReader(t, view)

	request := httptest.NewRequest(
		http.MethodGet,
		"/services/data/v63.0/sobjects/Opportunity/describe",
		nil,
	)
	request.Header.Set("Authorization", "Bearer token")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
	var body struct {
		Fields []struct {
			Name              string           `json:"name"`
			Calculated        bool             `json:"calculated"`
			CalculatedFormula *string          `json:"calculatedFormula"`
			PicklistValues    []map[string]any `json:"picklistValues"`
		} `json:"fields"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	var region *struct {
		Name              string           `json:"name"`
		Calculated        bool             `json:"calculated"`
		CalculatedFormula *string          `json:"calculatedFormula"`
		PicklistValues    []map[string]any `json:"picklistValues"`
	}
	for index := range body.Fields {
		if body.Fields[index].Name == "Region__c" {
			region = &body.Fields[index]
		}
	}
	if region == nil || !region.Calculated || region.CalculatedFormula == nil ||
		*region.CalculatedFormula != "TEXT(Account.Region__c)" ||
		len(region.PicklistValues) != 1 ||
		region.PicklistValues[0]["value"] != "APAC" ||
		region.PicklistValues[0]["label"] != "Asia Pacific" ||
		region.PicklistValues[0]["defaultValue"] != false ||
		!slices.Equal(region.PicklistValues[0]["tags"].([]any), []any{"regional"}) {
		t.Fatalf("formula describe field = %#v", region)
	}
}

func TestObjectDescribeTreatsV4ScalarFormulaFactAsCalculated(t *testing.T) {
	behavior := json.RawMessage(`{"objects":[{"apiName":"CampaignMember","label":"Campaign Member","fields":[{"apiName":"DisplayName","label":"Display Name","type":"string","createable":false,"updateable":false,"formula":"Name"}]}]}`)
	specs, err := searchObjectSpecsFromView(catalogsemantic.AppMetadataViewV3{
		EffectiveComponents: []catalogsemantic.EffectiveMetadataComponent{{
			ComponentKind:          catalogsemantic.EffectiveObjectSearchComponentKind,
			ComponentFormatVersion: effectiveObjectSearchComponentFormatV4,
			BusinessKey:            catalogsemantic.EffectiveObjectSearchComponentBusinessKey,
			CanonicalBehavior:      behavior,
		}},
	})
	if err != nil {
		t.Fatalf("decode V4 scalar formula fact: %v", err)
	}
	spec, found := specs["campaignmember"]
	if !found {
		t.Fatalf("decoded object specs = %#v", specs)
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "CampaignMember", CanRead: true,
		}},
		FieldPermissions: []permissionsemantic.FieldPermission{{
			ResourceType: "CampaignMember", FieldName: "DisplayName", CanRead: true,
		}},
	}
	projection := describeObjectProjection(
		"v63.0", spec, permissions.ObjectPermissions[0], permissions,
		recordTypeProjection{}, spec.ListFields, nil,
	)
	fields, ok := projection["fields"].([]map[string]any)
	if !ok || len(fields) != 1 ||
		fields[0]["type"] != "string" ||
		fields[0]["calculated"] != true ||
		fields[0]["calculatedFormula"] != "Name" {
		t.Fatalf("scalar formula describe fields = %#v", projection["fields"])
	}
}

func TestDescribeFormulaPicklistValuesRequireSourceObjectAndFieldRead(t *testing.T) {
	standardObject := "standard_object"
	source := searchObjectSpec{
		APIName: "Account", Label: "Account", TableType: &standardObject,
		Fields: []searchFieldSpec{{
			APIName: "Region__c", Label: "Region", Type: "picklist",
			Options: []searchFieldOption{{Value: "APAC", Label: "Asia Pacific"}},
		}},
	}
	formula := searchFieldSpec{
		APIName: "Region__c", Label: "Region", Type: "formula",
		Options:              []searchFieldOption{{Value: "LEAK", Label: "Leaked"}},
		PicklistValueSources: []searchFieldValueSource{{Object: "Account", Field: "Region__c"}},
	}
	specs := map[string]searchObjectSpec{"account": source}
	permissions := permissionsemantic.Response{}
	if values := describeAuthorizedPicklistValues(searchObjectSpec{}, formula, specs, permissions); len(values) != 0 {
		t.Fatalf("unreadable source object exposed values = %#v", values)
	}
	permissions.ObjectPermissions = []permissionsemantic.ObjectPermission{{ResourceType: "Account", CanRead: true}}
	permissions.FieldPermissions = []permissionsemantic.FieldPermission{{
		ResourceType: "Account", FieldName: "Region__c", CanRead: false,
	}}
	if values := describeAuthorizedPicklistValues(searchObjectSpec{}, formula, specs, permissions); len(values) != 0 {
		t.Fatalf("unreadable source field exposed values = %#v", values)
	}
	permissions.FieldPermissions[0].CanRead = true
	values := describeAuthorizedPicklistValues(searchObjectSpec{}, formula, specs, permissions)
	if len(values) != 1 || values[0].(map[string]any)["value"] != "APAC" {
		t.Fatalf("authorized source values = %#v", values)
	}
}

func TestObjectDescribeDoesNotDiscloseHiddenDerivedReferences(t *testing.T) {
	spec := searchObjectSpec{APIName: "Account", Label: "Account", Fields: []searchFieldSpec{
		{APIName: "BillingAddress", Label: "Billing Address", Type: "address"},
		{APIName: "BillingCity", Label: "Billing City", Type: "string", CompoundFieldName: "BillingAddress"},
		{APIName: "Country__c", Label: "Country", Type: "picklist", Options: []searchFieldOption{{Value: "US", Label: "United States"}}},
		{APIName: "State__c", Label: "State", Type: "picklist", ControllingField: "Country__c",
			Options:            []searchFieldOption{{Value: "CA", Label: "California"}},
			PicklistDependency: map[string][]string{"US": {"CA"}}},
		{APIName: "OwnerId", Label: "Owner", Type: "reference", ReferenceTo: []string{"User"}},
	}}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: spec.APIName, CanRead: true}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: spec.APIName, FieldName: "BillingCity", CanRead: true},
			{ResourceType: spec.APIName, FieldName: "State__c", CanRead: true},
			{ResourceType: spec.APIName, FieldName: "OwnerId", CanRead: true},
		},
	}
	result := describeObjectProjection("v63.0", spec, permissions.ObjectPermissions[0], permissions, testMasterRecordTypeProjection(), spec.ListFields, nil)
	fields := result["fields"].([]map[string]any)
	if len(fields) != 3 || fields[0]["compoundFieldName"] != nil ||
		fields[1]["controllerName"] != nil || fields[1]["dependentPicklist"] != false ||
		len(fields[1]["picklistValues"].([]any)) != 1 ||
		len(fields[2]["referenceTo"].([]string)) != 0 {
		t.Fatalf("hidden derived references leaked through describe: %#v", fields)
	}
}

func TestObjectDescribePreservesSearchUnavailableClassification(t *testing.T) {
	service, view := describeTestService(t, permissionsemantic.Response{})
	view.EffectiveComponents[0].CanonicalBehavior = json.RawMessage(`{"objects":`)
	service.appMetadataV3 = func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
		return view, nil
	}
	request := httptest.NewRequest(http.MethodGet, "/services/data/v63.0/sobjects/Lead/describe", nil)
	request.Header.Set("Authorization", "Bearer token")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusServiceUnavailable {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestObjectDescribeFailsClosedOnOlderComponentGeneration(t *testing.T) {
	service, _ := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
	})
	olderView := searchTestView(t, []searchObjectSpec{{
		APIName: "Lead", Label: "Lead",
		Fields: []searchFieldSpec{{APIName: "Name", Label: "Name", Type: "string"}},
	}})
	service.appMetadataV3 = func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
		return olderView, nil
	}
	_, err := service.readObjectDescribe(context.Background(), "bearer", "v63.0", "Lead")
	var failure *searchFailure
	if !errors.As(err, &failure) || failure.status != http.StatusServiceUnavailable {
		t.Fatalf("older component error = %v", err)
	}
}

func TestObjectDescribeFailsClosedOnMixedRevisionAndDisclosure(t *testing.T) {
	service, view := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
	})
	service.permissionMetadata.readEffective = func(
		context.Context, string, permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		response := permissionsemantic.Response{
			ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
			Receipts:          view.Receipts,
		}
		response.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
		response.Receipts.CatalogRevision++
		return response, nil
	}
	_, err := service.readObjectDescribe(context.Background(), "bearer", "v63.0", "Lead")
	var failure *commonsemantic.Failure
	if !errors.As(err, &failure) || failure.Category != commonsemantic.FailureStaleState {
		t.Fatalf("mixed revision error = %v", err)
	}

	service, _ = describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: false}},
	})
	_, err = service.readObjectDescribe(context.Background(), "bearer", "v63.0", "Lead")
	if !errors.As(err, &failure) || failure.Category != commonsemantic.FailureForbidden {
		t.Fatalf("disclosure error = %v", err)
	}
}

func TestObjectDescribeMapsMissingAndForbiddenFailures(t *testing.T) {
	for _, test := range []struct {
		name        string
		objectID    string
		permissions permissionsemantic.Response
		wantStatus  int
		wantCode    string
	}{
		{
			name: "missing object", objectID: "AbsentObject",
			permissions: permissionsemantic.Response{},
			wantStatus:  http.StatusNotFound, wantCode: crmwire.SFCodeNotFound,
		},
		{
			name: "unreadable object", objectID: "Lead",
			permissions: permissionsemantic.Response{ObjectPermissions: []permissionsemantic.ObjectPermission{{
				ResourceType: "Lead", CanRead: false,
			}}},
			wantStatus: http.StatusForbidden, wantCode: crmwire.SFCodeForbidden,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			service, _ := describeTestService(t, test.permissions)
			request := httptest.NewRequest(
				http.MethodGet,
				"/services/data/v63.0/sobjects/"+test.objectID+"/describe",
				nil,
			)
			request.Header.Set("Authorization", "Bearer token")
			response := httptest.NewRecorder()
			if !service.serveObjectDescribe(response, request) ||
				response.Code != test.wantStatus ||
				!strings.Contains(response.Body.String(), `"errorCode":"`+test.wantCode+`"`) {
				t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
			}
		})
	}
}

func TestMatchObjectDescribeRouteIsNarrow(t *testing.T) {
	route, ok := matchObjectDescribeRoute("/services/data/v63.0/sobjects/Lead/describe")
	if !ok || route.objectID != "Lead" || route.version != "v63.0" || route.global {
		t.Fatalf("route = %#v, %v", route, ok)
	}
	global, ok := matchObjectDescribeRoute("/services/data/v63.0/sobjects")
	if !ok || !global.global || global.version != "v63.0" || global.objectID != "" {
		t.Fatalf("global route = %#v, %v", global, ok)
	}
	for _, path := range []string{
		"/services/data/v64.0/sobjects",
		"/services/data/v63.0/sobjects/Lead/listviews",
		"/services/data/v63.0/sobjects/Lead/describe/layouts",
		"/services/data/v64.0/sobjects/Lead/describe",
	} {
		if _, matched := matchObjectDescribeRoute(path); matched {
			t.Fatalf("matched %s", path)
		}
	}
}

func TestObjectDescribeRejectsNewWorkAfterDrain(t *testing.T) {
	service, _ := describeTestService(t, permissionsemantic.Response{})
	service.requestTimeout = time.Second
	service.BeginDrain()
	request := httptest.NewRequest(http.MethodGet, "/services/data/v63.0/sobjects/Lead/describe", nil)
	request.Header.Set("Authorization", "Bearer token")
	response := httptest.NewRecorder()
	if !service.serveObjectDescribe(response, request) {
		t.Fatal("describe route was not handled")
	}
	if response.Code != http.StatusServiceUnavailable || service.inFlight.Load() != 0 || service.drainRejected.Load() != 1 {
		t.Fatalf("drain response=%d inFlight=%d rejected=%d", response.Code, service.inFlight.Load(), service.drainRejected.Load())
	}
}
