package sfcrmappservice

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

	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"
	accesssemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
	attachmentsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordattachmentsv1"
	defaultsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recorddefaultsv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordviewv1"
)

func TestRecordUIObjectInfoPermissionSelectionUsesImmutableReferences(t *testing.T) {
	standardObject := "standard_object"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", TableType: &standardObject,
			Fields: []searchFieldSpec{
				{APIName: "OwnerId", ReferenceTo: []string{"User"}},
				{APIName: "ParentId", ReferenceTo: []string{"Account", "Missing"}},
			},
		},
		"user": {APIName: "User", TableType: &standardObject},
	}
	selection, err := recordUIObjectInfoPermissionSelection(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", "User"}) {
		t.Fatalf("selection=%#v", selection)
	}
}

func TestBoundedRecordUIPermissionSelectionFailsClosedOverContractLimit(t *testing.T) {
	resources := make([]string, permissionsemantic.MaxSelectedResourceTypes+1)
	for index := range resources {
		resources[index] = fmt.Sprintf("Object%d", index)
	}
	if _, err := boundedRecordUIPermissionSelection(resources); err == nil {
		t.Fatal("over-limit Record UI permission resource set was accepted")
	}
}

func TestRecordUIReadAccessFailsClosedBeforeProjection(t *testing.T) {
	secret := "must-not-be-projected"
	read := recordsemantic.ReadResponse{
		Root: recordsemantic.Record{ObjectID: "Account", Fields: []recordsemantic.Field{{
			Name: "Secret__c", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &secret},
		}}},
		Access: &recordsemantic.RecordAccess{Read: false},
	}
	var failure *commonsemantic.Failure
	if err := validateRecordUIReadAccess(read); !errors.As(err, &failure) ||
		failure.Category != commonsemantic.FailureForbidden {
		t.Fatalf("denied record-ui read error=%#v", err)
	}
}

func TestProjectRecordUILayoutPreservesAuthorizedProdSemanticsByMode(t *testing.T) {
	tableType := "standard"
	spec := searchObjectSpec{
		APIName: "Account", TableType: &tableType,
		Fields: []searchFieldSpec{
			{APIName: "Name", Label: "Account Name", Required: true, Createable: true, Updateable: true},
			{APIName: "Rating", Label: "Rating", Createable: true, Updateable: true},
		},
	}
	permissions := permissionsemantic.Response{
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "Account", FieldName: "Rating", CanRead: true, CanEdit: true, CanCreate: true},
		},
	}
	layout := appPageLayout{
		ID: "account-layout", LayoutName: "Account Layout", ObjectAPIName: "Account",
		Sections: []appPageLayoutSection{{
			ID: "summary", Label: "Summary", Tab: "Details", Type: "Component",
			ComponentName: "crm.summary", ComponentConfig: map[string]any{"density": "compact"},
			Columns: 2, Fields: []string{"Name", "Rating"},
			FieldLabels: map[string]string{"Name": "Hidden account name", "Rating": "Customer tier"},
			FieldBehaviors: map[string]map[string]string{
				"Name": {"edit": "hidden"}, "Rating": {"edit": "required"},
			},
			VisibilityCondition: "record.Name != null",
		}},
		RelatedLists: []appRelatedList{{
			ID: "contacts", Label: "Contacts", ObjectAPIName: "Contact", LookupField: "AccountId",
			Fields: []string{"Name"}, PrefillMappings: map[string]string{"AccountId": "Id"},
		}},
		RecordPanels:  []appRecordPanel{{ID: "timeline", RendererID: "crm.timeline"}},
		HeaderActions: []appLayoutAction{{Name: "Edit", Label: "Edit", Type: "Standard"}},
		QuickActions:  []appLayoutAction{}, HeaderActionsAuthored: true, QuickActionsAuthored: true,
		RecordStates:         []appRecordStateRule{{ID: "open", When: "record.Name != null"}},
		PrimaryHeaderActions: []string{"host:edit"},
	}
	projected, err := projectRecordUILayout(
		layout, spec, newPageLayoutAuthorityIndex(
			map[string]searchObjectSpec{"account": spec}, permissions,
		),
		permissionsemantic.ObjectPermission{ResourceType: "Account", CanRead: true, CanCreate: true, CanEdit: true},
		permissions, "Edit",
	)
	if err != nil {
		t.Fatal(err)
	}
	sections := projected["sections"].([]map[string]any)
	if len(sections) != 1 || sections[0]["type"] != "Component" ||
		sections[0]["componentName"] != "crm.summary" || sections[0]["visibilityCondition"] != "record.Name != null" ||
		len(sections[0]["fieldLabels"].(map[string]string)) != 1 ||
		sections[0]["fieldLabels"].(map[string]string)["Rating"] != "Customer tier" {
		t.Fatalf("projected section semantics = %#v", sections)
	}
	rows := sections[0]["layoutRows"].([]map[string]any)
	items := rows[0]["layoutItems"].([]map[string]any)
	if len(items) != 1 || items[0]["label"] != "Customer tier" || items[0]["required"] != true {
		t.Fatalf("edit-mode items = %#v", items)
	}
	related := projected["relatedLists"].([]map[string]any)
	if related[0]["prefillMappings"].(map[string]string)["AccountId"] != "Id" ||
		len(projected["headerActions"].([]appLayoutAction)) != 1 ||
		projected["headerActionsAuthored"] != true || projected["quickActionsAuthored"] != true ||
		len(projected["recordStates"].([]appRecordStateRule)) != 1 ||
		!slices.Equal(projected["primaryHeaderActions"].([]string), []string{"host:edit"}) {
		t.Fatalf("projected layout semantics = %#v", projected)
	}
}

func TestAuthoredLayoutFieldBehaviorUsesProdModeVocabulary(t *testing.T) {
	behaviors := map[string]map[string]string{
		"Name": {"create": "required", "edit": "hidden", "detail": "required"},
	}
	if got := authoredLayoutFieldBehavior(behaviors, "Name", "Create"); got != "required" {
		t.Fatalf("Create behavior = %q", got)
	}
	if got := authoredLayoutFieldBehavior(behaviors, "Name", "Edit"); got != "hidden" {
		t.Fatalf("Edit behavior = %q", got)
	}
	if got := authoredLayoutFieldBehavior(behaviors, "Name", "View"); got != "" {
		t.Fatalf("View accepted required behavior = %q", got)
	}
	if got := authoredLayoutFieldBehavior(behaviors, "Name", "Unknown"); got != "" {
		t.Fatalf("unknown mode behavior = %q", got)
	}
}

func TestProjectRecordUILayoutPreservesReadableLookupExpansion(t *testing.T) {
	tableType := "standard"
	account := searchObjectSpec{
		APIName: "Account", TableType: &tableType,
		Fields: []searchFieldSpec{{
			APIName: "OwnerId", Label: "Owner", ReferenceTo: []string{"User"},
			Createable: true, Updateable: true,
		}},
	}
	user := searchObjectSpec{
		APIName: "User", TableType: &tableType,
		Fields: []searchFieldSpec{{APIName: "Name", Label: "Full Name"}},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true}, {ResourceType: "User", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "OwnerId", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "User", FieldName: "Name", CanRead: true},
		},
	}
	projected, err := projectRecordUILayout(
		appPageLayout{Sections: []appPageLayoutSection{{
			Columns: 1, Fields: []string{"OwnerId.Name"},
			FieldLabels: map[string]string{"OwnerId.Name": "Account Owner"},
		}}},
		account, newPageLayoutAuthorityIndex(
			map[string]searchObjectSpec{"account": account, "user": user}, permissions,
		),
		permissionsemantic.ObjectPermission{ResourceType: "Account", CanRead: true, CanCreate: true, CanEdit: true},
		permissions, "View",
	)
	if err != nil {
		t.Fatal(err)
	}
	section := projected["sections"].([]map[string]any)[0]
	item := section["layoutRows"].([]map[string]any)[0]["layoutItems"].([]map[string]any)[0]
	component := item["layoutComponents"].([]map[string]any)[0]
	details := component["details"].(map[string]any)
	if item["label"] != "Account Owner" || component["apiName"] != "OwnerId.Name" ||
		details["lookupExpansion"] != true || details["parentField"] != "OwnerId" ||
		details["displayField"] != "Name" || details["editable"] != true {
		t.Fatalf("lookup expansion = %#v", item)
	}
}

func BenchmarkAuthorizeAndProjectPageLayout(b *testing.B) {
	const fieldCount = 200
	standardObject := "standard"
	fields := make([]searchFieldSpec, fieldCount)
	fieldPermissions := make([]permissionsemantic.FieldPermission, fieldCount)
	sections := make([]appPageLayoutSection, 20)
	for index := range fields {
		name := fmt.Sprintf("Field_%03d__c", index)
		fields[index] = searchFieldSpec{
			APIName: name, Label: name, Type: "string", Createable: true, Updateable: true,
		}
		fieldPermissions[index] = permissionsemantic.FieldPermission{
			ResourceType: "Benchmark__c", FieldName: name, CanRead: true, CanEdit: true, CanCreate: true,
		}
		section := index / 10
		sections[section].ID = fmt.Sprintf("section-%02d", section)
		sections[section].Columns = 2
		sections[section].Fields = append(sections[section].Fields, name)
	}
	spec := searchObjectSpec{
		APIName: "Benchmark__c", TableType: &standardObject, Fields: fields,
	}
	specs := map[string]searchObjectSpec{"benchmark__c": spec}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Benchmark__c", CanRead: true, CanCreate: true, CanEdit: true,
		}},
		FieldPermissions: fieldPermissions,
	}
	layout := appPageLayout{
		ObjectAPIName: "Benchmark__c", LayoutName: "Benchmark", IsDefault: true,
		Sections: sections,
	}
	b.ReportAllocs()
	b.ResetTimer()
	for iteration := 0; iteration < b.N; iteration++ {
		authorityIndex := newPageLayoutAuthorityIndex(specs, permissions)
		authorized, err := authorizePageLayoutWithIndex(layout, specs, permissions, authorityIndex, testCurrentListFields(specs))
		if err != nil {
			b.Fatal(err)
		}
		projected, err := projectRecordUILayout(
			authorized, spec, authorityIndex, permissions.ObjectPermissions[0], permissions, "Edit",
		)
		if err != nil {
			b.Fatal(err)
		}
		if len(projected["sections"].([]map[string]any)) != len(sections) {
			b.Fatal("projection lost sections")
		}
	}
}

func standaloneRecordUIMetadataTestService(t *testing.T) *Service {
	t.Helper()
	searchable := true
	standardObject := "standard_object"
	systemField := true
	appField := false
	siblingParent := "CampaignId"
	view := createInputSearchTestView(t, []searchObjectSpec{
		{
			APIName: "Account", Label: "Account", PluralLabel: "Accounts", KeyPrefix: "001",
			TableType: &standardObject, Searchable: &searchable, CreateRequiredFields: &[]string{},
			Fields: []searchFieldSpec{
				{APIName: "Id", Label: "Account ID", Type: "string", IsSystem: &systemField},
				{APIName: "Name", Label: "Account Name", Type: "string", IsNameField: true, IsSystem: &appField, Createable: true, Updateable: true},
				{APIName: "Secret__c", Label: "Secret", Type: "string", IsSystem: &appField, Createable: true, Updateable: true},
			},
		},
		{
			APIName: "CampaignMember", Label: "Campaign Member", PluralLabel: "Campaign Members", KeyPrefix: "00v",
			TableType: &standardObject, Searchable: &searchable, CreateRequiredFields: &[]string{},
			Fields: []searchFieldSpec{
				{APIName: "Id", Label: "Campaign Member ID", Type: "string", IsSystem: &systemField},
				{APIName: "CampaignId", Label: "Campaign", Type: "string", IsSystem: &appField, Createable: true, Updateable: true},
				{APIName: "Status", Label: "Status", Type: "string", IsSystem: &appField, Createable: true, Updateable: true},
			},
		},
		{
			APIName: "CampaignMemberStatus", Label: "Campaign Member Status", PluralLabel: "Campaign Member Statuses", KeyPrefix: "07m",
			TableType: &standardObject, Searchable: &searchable, CreateRequiredFields: &[]string{},
			Fields: []searchFieldSpec{
				{APIName: "Id", Label: "Campaign Member Status ID", Type: "string", IsSystem: &systemField},
				{APIName: "CampaignId", Label: "Campaign", Type: "reference", IsSystem: &appField, Createable: true, Updateable: true},
				{APIName: "SortOrder", Label: "Sort Order", Type: "integer", IsSystem: &appField, Createable: true, Updateable: true, SiblingMaxParentField: &siblingParent},
			},
		},
	})
	view.AppComponents = append(view.AppComponents, flowActionsTestComponent(
		t, appPageLayoutComponentKind, "Account/default",
		`{"objectApiName":"Account","layoutName":"Account Layout","isDefault":true,"compactLayout":["Name","Secret__c"],"tabs":["Details","Qualification","Activity"],"recordPanels":[{"id":"crm.activityTimeline","rendererId":"app-pack:crm/record-panel/activity-timeline","tabId":"activity","tabLabel":"Activity","order":100},{"id":"visible-panel","rendererId":"visible-renderer","visibilityCondition":"record.Name != null"},{"id":"hidden-panel","rendererId":"hidden-renderer","visibilityCondition":"record.Secret__c != null"}],"sections":[{"id":"qualification","label":"Qualification","tab":"Qualification","columns":2,"fields":["Name","Secret__c"]}],"relatedLists":[]}`,
	))
	campaignMemberLayout := json.RawMessage(
		`{"objectApiName":"CampaignMember","layoutName":"Campaign Member Layout","isDefault":true,"compactLayout":["CampaignId"],"sections":[{"id":"details","label":"Details","columns":2,"fields":["CampaignId","Status"]}],"relatedLists":[]}`,
	)
	view.EffectiveComponents = append(
		view.EffectiveComponents,
		catalogsemantic.EffectiveMetadataComponent{
			ComponentKind:          catalogsemantic.EffectivePageLayoutComponentKind,
			ComponentFormatVersion: 1,
			BusinessKey:            "CampaignMember/default",
			ContentDigest: catalogsemantic.ComputeEffectiveMetadataComponentDigest(
				campaignMemberLayout,
			),
			CanonicalBehavior: campaignMemberLayout,
		},
		catalogsemantic.EffectiveMetadataComponent{
			ComponentKind:          catalogsemantic.EffectivePageLayoutComponentKind,
			ComponentFormatVersion: 1,
			BusinessKey:            "CampaignMemberStatus/default",
			CanonicalBehavior: json.RawMessage(
				`{"objectApiName":"CampaignMemberStatus","layoutName":"Campaign Member Status Layout","isDefault":true,"compactLayout":["SortOrder"],"sections":[{"id":"details","label":"Details","columns":2,"fields":["CampaignId","SortOrder"]}],"relatedLists":[]}`,
			),
		},
	)
	view.EffectiveComponents[len(view.EffectiveComponents)-1].ContentDigest =
		catalogsemantic.ComputeEffectiveMetadataComponentDigest(
			view.EffectiveComponents[len(view.EffectiveComponents)-1].CanonicalBehavior,
		)
	sort.Slice(view.AppComponents, func(left, right int) bool {
		return view.AppComponents[left].ComponentID < view.AppComponents[right].ComponentID
	})
	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("record UI metadata test view is invalid: %v", err)
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true, CanCreate: true, CanEdit: true},
			{ResourceType: "CampaignMember", CanRead: true, CanCreate: true, CanEdit: true},
			{ResourceType: "CampaignMemberStatus", CanRead: true, CanCreate: true, CanEdit: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Id", CanRead: true},
			{ResourceType: "Account", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "Account", FieldName: "Secret__c", CanRead: false, CanEdit: true, CanCreate: true},
			{ResourceType: "CampaignMember", FieldName: "Id", CanRead: true},
			{ResourceType: "CampaignMember", FieldName: "CampaignId", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "CampaignMember", FieldName: "Status", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "CampaignMemberStatus", FieldName: "Id", CanRead: true},
			{ResourceType: "CampaignMemberStatus", FieldName: "CampaignId", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "CampaignMemberStatus", FieldName: "SortOrder", CanRead: true, CanEdit: true, CanCreate: true},
		},
		Receipts: view.Receipts,
	}
	sort.Slice(permissions.ObjectPermissions, func(left, right int) bool {
		return permissions.ObjectPermissions[left].ResourceType < permissions.ObjectPermissions[right].ResourceType
	})
	sort.Slice(permissions.FieldPermissions, func(left, right int) bool {
		leftKey := permissions.FieldPermissions[left].ResourceType + "\x00" + permissions.FieldPermissions[left].FieldName
		rightKey := permissions.FieldPermissions[right].ResourceType + "\x00" + permissions.FieldPermissions[right].FieldName
		return leftKey < rightKey
	})
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	return &Service{
		resolvePageLayoutRuntime:    testPageLayoutResolver(t, view),
		readObjectListFieldsRuntime: testObjectListFieldsReader(t, view),
		appMetadataV3: func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
			return view, nil
		},
		permissionMetadata: &RuntimePermissionMetadataClient{readEffective: func(
			_ context.Context, bearer string, request permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			if bearer != "end-user" || request.MinCatalogRevision == nil ||
				*request.MinCatalogRevision != view.Receipts.CatalogRevision ||
				request.Selection == nil ||
				!slices.Equal(request.Selection.Sections, []permissionsemantic.Section{
					permissionsemantic.SectionObjectPermissions,
					permissionsemantic.SectionFieldPermissions,
					permissionsemantic.SectionRecordTypePermissions,
				}) || len(request.Selection.ResourceTypes) != 1 {
				t.Fatalf("permission request=%#v bearer=%q", request, bearer)
			}
			selected := strings.ToLower(request.Selection.ResourceTypes[0])
			response := permissionsemantic.Response{Receipts: permissions.Receipts}
			for _, permission := range permissions.ObjectPermissions {
				if strings.ToLower(permission.ResourceType) == selected {
					response.ObjectPermissions = append(response.ObjectPermissions, permission)
				}
			}
			for _, permission := range permissions.FieldPermissions {
				if strings.ToLower(permission.ResourceType) == selected {
					response.FieldPermissions = append(response.FieldPermissions, permission)
				}
			}
			for _, permission := range permissions.RecordTypePermissions {
				if strings.ToLower(permission.ResourceType) == selected {
					response.RecordTypePermissions = append(response.RecordTypePermissions, permission)
				}
			}
			return response, nil
		}},
		previewCreateDefaults: func(
			_ context.Context, bearer string, request defaultsemantic.Request,
		) (defaultsemantic.Response, error) {
			if bearer != "end-user" || request.MinCatalogRevision == nil ||
				*request.MinCatalogRevision != view.Receipts.CatalogRevision {
				t.Fatalf("create-default request=%#v bearer=%q", request, bearer)
			}
			receipts := view.Receipts
			receipts.CapabilityVersion = defaultsemantic.CapabilityName
			return defaultsemantic.Response{Receipts: receipts}, nil
		},
		requestTimeout: time.Second,
	}
}

func getRecordUIMetadata(t *testing.T, service *Service, path string) *httptest.ResponseRecorder {
	t.Helper()
	request := httptest.NewRequest(http.MethodGet, path, nil)
	request.Header.Set("Authorization", "Bearer end-user")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	return response
}

type recordTypeInfoTestWire struct {
	ID        string `json:"recordTypeId"`
	Available bool   `json:"available"`
	Default   bool   `json:"defaultRecordTypeMapping"`
	Master    bool   `json:"master"`
}

func TestStandaloneRecordUIMetadataServesAccountAndCampaignMember(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	baseListFieldsReader := service.readObjectListFieldsRuntime
	service.readObjectListFieldsRuntime = func(
		ctx context.Context, bearer string, request pagelayoutsemantic.ObjectListFieldsRequest,
	) (pagelayoutsemantic.ObjectListFieldsResponse, error) {
		response, err := baseListFieldsReader(ctx, bearer, request)
		if err != nil {
			return response, err
		}
		for index := range response.Objects {
			if strings.EqualFold(response.Objects[index].ObjectID, "Account") {
				response.Objects[index].OrderedFieldIDs = []string{"Name"}
			}
		}
		return response, nil
	}
	accountInfo := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/object-info/Account")
	if accountInfo.Code != http.StatusOK {
		t.Fatalf("account object-info status=%d body=%s", accountInfo.Code, accountInfo.Body.String())
	}
	var info struct {
		APIName    string                     `json:"apiName"`
		Fields     map[string]json.RawMessage `json:"fields"`
		ListFields []string                   `json:"listFields"`
	}
	if err := json.Unmarshal(accountInfo.Body.Bytes(), &info); err != nil {
		t.Fatal(err)
	}
	if info.APIName != "Account" || !slices.Equal(info.ListFields, []string{"Name"}) ||
		len(info.Fields) != 2 || len(info.Fields["Name"]) == 0 ||
		len(info.Fields["Secret__c"]) != 0 {
		t.Fatalf("account object-info=%#v", info)
	}

	accountLayout := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/layout/Account?type=Edit")
	if accountLayout.Code != http.StatusOK {
		t.Fatalf("account layout status=%d body=%s", accountLayout.Code, accountLayout.Body.String())
	}
	var layout struct {
		ObjectAPIName string   `json:"objectApiName"`
		Mode          string   `json:"mode"`
		Tabs          []string `json:"tabs"`
		RecordPanels  []struct {
			ID         string `json:"id"`
			RendererID string `json:"rendererId"`
			TabID      string `json:"tabId"`
			TabLabel   string `json:"tabLabel"`
			Order      int    `json:"order"`
			Condition  string `json:"visibilityCondition"`
		} `json:"recordPanels"`
		Sections []struct {
			Tab        string `json:"tab"`
			LayoutRows []struct {
				LayoutItems []struct {
					LayoutComponents []struct {
						APIName string `json:"apiName"`
					} `json:"layoutComponents"`
				} `json:"layoutItems"`
			} `json:"layoutRows"`
		} `json:"sections"`
	}
	if err := json.Unmarshal(accountLayout.Body.Bytes(), &layout); err != nil {
		t.Fatal(err)
	}
	if layout.ObjectAPIName != "Account" || layout.Mode != "Edit" ||
		!slices.Equal(layout.Tabs, []string{"Details", "Qualification", "Activity"}) ||
		len(layout.Sections) != 1 || layout.Sections[0].Tab != "Qualification" ||
		len(layout.Sections[0].LayoutRows) != 1 || len(layout.Sections[0].LayoutRows[0].LayoutItems) != 1 ||
		layout.Sections[0].LayoutRows[0].LayoutItems[0].LayoutComponents[0].APIName != "Name" ||
		len(layout.RecordPanels) != 2 || layout.RecordPanels[0].ID != "crm.activityTimeline" ||
		layout.RecordPanels[0].RendererID != "app-pack:crm/record-panel/activity-timeline" ||
		layout.RecordPanels[0].TabID != "activity" || layout.RecordPanels[0].TabLabel != "Activity" ||
		layout.RecordPanels[0].Order != 100 || layout.RecordPanels[1].ID != "visible-panel" ||
		layout.RecordPanels[1].Condition != "record.Name != null" {
		t.Fatalf("account layout=%#v", layout)
	}

	campaignMember := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/object-info/CampaignMember")
	if campaignMember.Code != http.StatusOK {
		t.Fatalf("campaign member object-info status=%d body=%s", campaignMember.Code, campaignMember.Body.String())
	}
	campaignMemberLayout := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/layout/CampaignMember?type=Create")
	if campaignMemberLayout.Code != http.StatusOK {
		t.Fatalf("campaign member layout status=%d body=%s", campaignMemberLayout.Code, campaignMemberLayout.Body.String())
	}
	var campaignLayout struct {
		ObjectAPIName string `json:"objectApiName"`
		Mode          string `json:"mode"`
	}
	if err := json.Unmarshal(campaignMemberLayout.Body.Bytes(), &campaignLayout); err != nil {
		t.Fatal(err)
	}
	if campaignLayout.ObjectAPIName != "CampaignMember" || campaignLayout.Mode != "Create" {
		t.Fatalf("campaign member layout=%#v", campaignLayout)
	}
	campaignMemberObjectLayouts := getRecordUIMetadata(
		t, service, "/services/data/v63.0/sobjects/CampaignMember/describe/layouts",
	)
	if campaignMemberObjectLayouts.Code != http.StatusOK ||
		!strings.Contains(campaignMemberObjectLayouts.Body.String(), `"name":"Campaign Member Layout"`) {
		t.Fatalf(
			"campaign member object layouts status=%d body=%s",
			campaignMemberObjectLayouts.Code,
			campaignMemberObjectLayouts.Body.String(),
		)
	}
}

func TestStandaloneRecordUIMetadataReturns404ForUnknownObject(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	for _, path := range []string{
		"/services/data/v63.0/ui-api/object-info/Unknown",
		"/services/data/v63.0/ui-api/layout/Unknown?type=Create",
	} {
		response := getRecordUIMetadata(t, service, path)
		if response.Code != http.StatusNotFound {
			t.Fatalf("path=%s status=%d body=%s", path, response.Code, response.Body.String())
		}
	}
}

func TestCampaignMemberRecordDefaultsCreateUsesAuthorizedCatalogProjection(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	resolver := service.resolvePageLayoutRuntime
	service.resolvePageLayoutRuntime = func(
		ctx context.Context,
		bearer string,
		request pagelayoutsemantic.Request,
	) (pagelayoutsemantic.Response, error) {
		if len(request.RecordTypeIDs) != 0 {
			t.Fatalf("synthetic Master reached Runtime as selectors %v", request.RecordTypeIDs)
		}
		return resolver(ctx, bearer, request)
	}
	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-defaults/create/CampaignMember?recordTypeId="+masterRecordTypeID)
	if response.Code != http.StatusOK {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
	var body struct {
		ObjectAPIName string `json:"objectApiName"`
		RecordTypeID  string `json:"recordTypeId"`
		Record        struct {
			APIName string `json:"apiName"`
			Fields  map[string]struct {
				DisplayValue any `json:"displayValue"`
				Value        any `json:"value"`
			} `json:"fields"`
		} `json:"record"`
		ObjectInfo struct {
			APIName string `json:"apiName"`
			Fields  map[string]struct {
				Createable bool `json:"createable"`
			} `json:"fields"`
		} `json:"objectInfo"`
		Layout struct {
			ObjectAPIName string `json:"objectApiName"`
			Mode          string `json:"mode"`
			Sections      []struct {
				LayoutRows []struct {
					LayoutItems []struct {
						LayoutComponents []struct {
							APIName string `json:"apiName"`
						} `json:"layoutComponents"`
					} `json:"layoutItems"`
				} `json:"layoutRows"`
			} `json:"sections"`
		} `json:"layout"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body.ObjectAPIName != "CampaignMember" || body.RecordTypeID != masterRecordTypeID ||
		body.Record.APIName != "CampaignMember" || len(body.Record.Fields) != 2 ||
		body.ObjectInfo.APIName != "CampaignMember" || body.Layout.ObjectAPIName != "CampaignMember" ||
		body.Layout.Mode != "Create" || len(body.Layout.Sections) != 1 {
		t.Fatalf("record defaults=%#v", body)
	}
	if !body.ObjectInfo.Fields["CampaignId"].Createable ||
		len(body.Layout.Sections[0].LayoutRows) != 1 ||
		len(body.Layout.Sections[0].LayoutRows[0].LayoutItems) != 2 ||
		body.Layout.Sections[0].LayoutRows[0].LayoutItems[0].LayoutComponents[0].APIName != "CampaignId" {
		t.Fatalf("frontend metadata shape=%#v", body)
	}
	for _, field := range []string{"CampaignId", "Status"} {
		value, found := body.Record.Fields[field]
		if !found || value.Value != nil || value.DisplayValue != nil {
			t.Fatalf("field %s defaults=%#v found=%v", field, value, found)
		}
	}
}

func TestRecordDefaultsCreateFailsClosed(t *testing.T) {
	for _, test := range []struct {
		name       string
		path       string
		mutate     func(permissionsemantic.Response) permissionsemantic.Response
		wantStatus int
	}{
		{
			name:       "unknown record type",
			path:       "/services/data/v63.0/ui-api/record-defaults/create/CampaignMember?recordTypeId=rt_unknown",
			wantStatus: http.StatusNotFound,
		},
		{
			name:       "unknown object",
			path:       "/services/data/v63.0/ui-api/record-defaults/create/Unknown?recordTypeId=" + masterRecordTypeID,
			wantStatus: http.StatusNotFound,
		},
		{
			name: "create forbidden",
			path: "/services/data/v63.0/ui-api/record-defaults/create/CampaignMember?recordTypeId=" + masterRecordTypeID,
			mutate: func(response permissionsemantic.Response) permissionsemantic.Response {
				for index := range response.ObjectPermissions {
					if response.ObjectPermissions[index].ResourceType == "CampaignMember" {
						response.ObjectPermissions[index].CanCreate = false
					}
				}
				return response
			},
			wantStatus: http.StatusForbidden,
		},
		{
			name: "mixed catalog revision",
			path: "/services/data/v63.0/ui-api/record-defaults/create/CampaignMember?recordTypeId=" + masterRecordTypeID,
			mutate: func(response permissionsemantic.Response) permissionsemantic.Response {
				response.Receipts.CatalogRevision++
				return response
			},
			wantStatus: http.StatusServiceUnavailable,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			service := standaloneRecordUIMetadataTestService(t)
			if test.mutate != nil {
				read := service.permissionMetadata.readEffective
				service.permissionMetadata.readEffective = func(
					ctx context.Context, bearer string, request permissionsemantic.Request,
				) (permissionsemantic.Response, error) {
					response, err := read(ctx, bearer, request)
					if err != nil {
						return permissionsemantic.Response{}, err
					}
					return test.mutate(response), nil
				}
			}
			response := getRecordUIMetadata(t, service, test.path)
			if response.Code != test.wantStatus {
				t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
			}
		})
	}
}

func TestRecordDefaultsRejectsNonCanonicalRecordTypeBeforeRuntime(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	runtimeCalls := 0
	resolve := service.resolvePageLayoutRuntime
	service.resolvePageLayoutRuntime = func(
		ctx context.Context, bearer string, request pagelayoutsemantic.Request,
	) (pagelayoutsemantic.Response, error) {
		runtimeCalls++
		return resolve(ctx, bearer, request)
	}
	for _, selector := range []string{
		"%20rt-enterprise",
		strings.Repeat("x", pagelayoutsemantic.MaxOpaqueIdentifierBytes+1),
	} {
		response := getRecordUIMetadata(t, service,
			"/services/data/v63.0/ui-api/record-defaults/create/CampaignMember?recordTypeId="+selector)
		if response.Code != http.StatusBadRequest {
			t.Fatalf("selector=%q status=%d body=%s", selector, response.Code, response.Body.String())
		}
	}
	if runtimeCalls != 0 {
		t.Fatalf("invalid RecordType selectors reached Runtime %d times", runtimeCalls)
	}
}

func TestRecordDefaultsCreateAcceptsOrdinaryRelatedListContext(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-defaults/create/CampaignMember?recordTypeId="+masterRecordTypeID+
			"&parentFieldApiName=CampaignId&parentRecordId=campaign-1")
	if response.Code != http.StatusOK {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
	if !strings.Contains(response.Body.String(), `"objectApiName":"CampaignMember"`) {
		t.Fatalf("record defaults body=%s", response.Body.String())
	}
}

func TestRecordDefaultsLeavesSiblingMaxForRuntimeAuthority(t *testing.T) {
	parent := "CampaignId"
	fields, err := recordDefaultFields(recordUIAuthority{
		spec: searchObjectSpec{APIName: "CampaignMemberStatus", Fields: []searchFieldSpec{{
			APIName: "SortOrder", Createable: true, SiblingMaxParentField: &parent,
		}}},
		objectPermission: permissionsemantic.ObjectPermission{ResourceType: "CampaignMemberStatus", CanCreate: true},
		permissions: permissionsemantic.Response{FieldPermissions: []permissionsemantic.FieldPermission{{
			ResourceType: "CampaignMemberStatus", FieldName: "SortOrder", CanRead: true, CanEdit: true, CanCreate: true,
		}}},
	}, projectedRecordType{RecordTypeID: masterRecordTypeID, Name: "Master"})
	if err != nil {
		t.Fatal(err)
	}
	projected, ok := fields["SortOrder"].(map[string]any)
	if !ok || projected["value"] != nil {
		t.Fatalf("Catalog projection invented sibling-max value: %#v", fields)
	}
}

func TestCampaignMemberStatusRecordDefaultsUsesRuntimeSiblingMaxAndProvenance(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	called := 0
	service.previewCreateDefaults = func(
		_ context.Context, bearer string, request defaultsemantic.Request,
	) (defaultsemantic.Response, error) {
		called++
		if bearer != "end-user" || request.ObjectID != "CampaignMemberStatus" ||
			request.ParentFieldID == nil || *request.ParentFieldID != "CampaignId" ||
			request.ParentRecordID == nil || *request.ParentRecordID != "campaign-1" ||
			request.MinCatalogRevision == nil {
			t.Fatalf("create-default request=%#v bearer=%q", request, bearer)
		}
		next := int64(901)
		view, err := service.appMetadataV3(context.Background(), catalogsemantic.GetRequest{})
		if err != nil {
			t.Fatal(err)
		}
		receipts := view.Receipts
		receipts.CapabilityVersion = defaultsemantic.CapabilityName
		return defaultsemantic.Response{
			Fields: []defaultsemantic.Field{{
				Name: "SortOrder", Kind: defaultsemantic.ValueInteger, Integer: &next,
				Provenance: defaultsemantic.ProvenanceSiblingMaxPlusOne,
			}},
			Receipts: receipts,
		}, nil
	}

	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-defaults/create/CampaignMemberStatus?recordTypeId="+masterRecordTypeID+
			"&parentFieldApiName=CampaignId&parentRecordId=campaign-1")
	if response.Code != http.StatusOK || called != 1 {
		t.Fatalf("status=%d calls=%d body=%s", response.Code, called, response.Body.String())
	}
	var body struct {
		Record struct {
			Fields map[string]struct {
				Value any `json:"value"`
			} `json:"fields"`
		} `json:"record"`
		SiblingMaxDefaultedFields []string `json:"siblingMaxDefaultedFields"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body.Record.Fields["SortOrder"].Value != float64(901) ||
		!slices.Equal(body.SiblingMaxDefaultedFields, []string{"SortOrder"}) {
		t.Fatalf("record defaults=%#v", body)
	}
}

func TestRecordDefaultsFailsClosedOnMixedSecurityEpochAfterReconciliation(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	readEffective := service.permissionMetadata.readEffective
	service.permissionMetadata.readEffective = func(
		ctx context.Context, bearer string, request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		response, err := readEffective(ctx, bearer, request)
		if err != nil {
			return permissionsemantic.Response{}, err
		}
		response.Receipts.SecurityEpoch++
		return response, nil
	}
	service.reconcileAppMetadataV3 = func(
		_ context.Context, expected commonsemantic.Receipts, actual commonsemantic.Receipts,
	) (bool, error) {
		if expected.SecurityEpoch+1 != actual.SecurityEpoch {
			t.Fatalf("reconcile expected=%#v actual=%#v", expected, actual)
		}
		return true, nil
	}
	service.previewCreateDefaults = func(
		_ context.Context, _ string, _ defaultsemantic.Request,
	) (defaultsemantic.Response, error) {
		view, err := service.appMetadataV3(context.Background(), catalogsemantic.GetRequest{})
		if err != nil {
			t.Fatal(err)
		}
		next := int64(901)
		receipts := view.Receipts
		receipts.CapabilityVersion = defaultsemantic.CapabilityName
		return defaultsemantic.Response{
			Fields: []defaultsemantic.Field{{
				Name: "SortOrder", Kind: defaultsemantic.ValueInteger, Integer: &next,
				Provenance: defaultsemantic.ProvenanceSiblingMaxPlusOne,
			}},
			Receipts: receipts,
		}, nil
	}

	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-defaults/create/CampaignMemberStatus?recordTypeId="+masterRecordTypeID+
			"&parentFieldApiName=CampaignId&parentRecordId=campaign-1")
	if response.Code != http.StatusServiceUnavailable || strings.Contains(response.Body.String(), "901") {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestRecordDefaultsFailsClosedWithoutRuntimeAuthority(t *testing.T) {
	service := standaloneRecordUIMetadataTestService(t)
	service.previewCreateDefaults = nil
	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-defaults/create/CampaignMemberStatus?recordTypeId="+masterRecordTypeID+
			"&parentFieldApiName=CampaignId&parentRecordId=campaign-1")
	if response.Code != http.StatusServiceUnavailable {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestRecordDefaultsProjectsAuthoredCatalogDefault(t *testing.T) {
	var spec searchObjectSpec
	if err := json.Unmarshal([]byte(`{
		"apiName":"Example","fields":[
			{"apiName":"Enabled__c","type":"boolean","createable":true,"defaultValue":"0"}
		]
	}`), &spec); err != nil {
		t.Fatal(err)
	}
	fields, err := recordDefaultFields(recordUIAuthority{
		spec:             spec,
		objectPermission: permissionsemantic.ObjectPermission{ResourceType: "Example", CanCreate: true},
		permissions: permissionsemantic.Response{FieldPermissions: []permissionsemantic.FieldPermission{{
			ResourceType: "Example", FieldName: "Enabled__c", CanRead: true, CanEdit: true, CanCreate: true,
		}}},
	}, projectedRecordType{RecordTypeID: masterRecordTypeID, Name: "Master"})
	if err != nil {
		t.Fatal(err)
	}
	field, found := fields["Enabled__c"].(map[string]any)
	if !found || field["value"] != false || field["displayValue"] != nil {
		t.Fatalf("Catalog-authored default=%#v found=%v", field, found)
	}
}

func TestConcreteRecordTypesProjectAcrossDescribeLayoutsAndRecordUI(t *testing.T) {
	fixture := loadRecordTypeDisplayParityFixture(t)
	searchable := true
	standardObject := "standard_object"
	systemField := true
	appField := false
	view := createInputSearchTestView(t, []searchObjectSpec{{
		APIName: "Order", Label: "Order", PluralLabel: "Orders", KeyPrefix: "801",
		TableType: &standardObject, Searchable: &searchable, CreateRequiredFields: &[]string{},
		Fields: []searchFieldSpec{
			{APIName: "Id", Label: "Order ID", Type: "string", IsSystem: &systemField},
			{APIName: "Name", Label: "Order Name", Type: "string", IsNameField: true, IsSystem: &appField},
			{APIName: fixture.RecordTypeField, Label: "Record Type", Type: "reference", ReferenceTo: []string{fixture.RelationshipField}, IsSystem: &systemField, Createable: true, Updateable: true},
		},
	}})
	view.AppComponents = append(view.AppComponents,
		flowActionsTestComponent(t, appPageLayoutComponentKind, "Order/default",
			`{"objectApiName":"Order","layoutName":"Order Layout","isDefault":true,"compactLayout":["Name"],"sections":[{"id":"details","label":"Details","columns":1,"fields":["Name"]}],"relatedLists":[]}`),
		flowActionsTestComponent(t, appRecordTypeComponentKind, "record-types",
			`{"recordTypes":[{"objectApiName":"Order","types":[{"name":"New"},{"name":"Renewal"}]}]}`),
	)
	sort.Slice(view.AppComponents, func(left, right int) bool {
		return view.AppComponents[left].ComponentID < view.AppComponents[right].ComponentID
	})
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
	if err := catalogsemantic.ValidateAppMetadataViewV3(view); err != nil {
		t.Fatalf("record-type view is invalid: %v", err)
	}
	newID := fixture.RecordTypeID
	const renewalID = "rt-renewal-opaque"
	view = withTestEffectiveRecordTypes(t, view, "Order",
		effectiveRecordTypeDefinition{RecordTypeID: newID, Name: fixture.RecordTypeName, DeveloperName: fixture.RecordTypeName},
		effectiveRecordTypeDefinition{RecordTypeID: renewalID, Name: "Renewal", DeveloperName: "Renewal"},
	)
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
	if err := catalogsemantic.ValidateAppMetadataViewV3(view); err != nil {
		t.Fatalf("record-type effective view is invalid: %v", err)
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Order", CanRead: true, CanCreate: true}},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Order", FieldName: "Id", CanRead: true},
			{ResourceType: "Order", FieldName: "Name", CanRead: true},
			{ResourceType: "Order", FieldName: fixture.RecordTypeField, CanRead: true, CanEdit: true, CanCreate: true},
		},
		RecordTypePermissions: []permissionsemantic.RecordTypePermission{
			testRecordTypePermission("Order", newID, true, true),
			testRecordTypePermission("Order", renewalID, false, false),
		},
		Receipts: view.Receipts,
	}
	sort.Slice(permissions.RecordTypePermissions, func(left, right int) bool {
		return permissions.RecordTypePermissions[left].RecordTypeID < permissions.RecordTypePermissions[right].RecordTypeID
	})
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	baseLayoutResolver := testPageLayoutResolver(t, view)
	var resolvedRecordTypes []string
	var resolvedBatches [][]string
	service := &Service{
		resolvePageLayoutRuntime: func(
			ctx context.Context,
			bearer string,
			request pagelayoutsemantic.Request,
		) (pagelayoutsemantic.Response, error) {
			resolvedBatches = append(resolvedBatches, append([]string(nil), request.RecordTypeIDs...))
			if len(request.RecordTypeIDs) == 0 {
				resolvedRecordTypes = append(resolvedRecordTypes, "")
			} else {
				resolvedRecordTypes = append(resolvedRecordTypes, request.RecordTypeIDs...)
			}
			return baseLayoutResolver(ctx, bearer, request)
		},
		readObjectListFieldsRuntime: testObjectListFieldsReader(t, view),
		appMetadataV3: func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
			return view, nil
		},
		permissionMetadata: &RuntimePermissionMetadataClient{readEffective: func(
			context.Context, string, permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			return permissions, nil
		}},
		previewCreateDefaults: func(
			context.Context, string, defaultsemantic.Request,
		) (defaultsemantic.Response, error) {
			receipts := view.Receipts
			receipts.CapabilityVersion = defaultsemantic.CapabilityName
			return defaultsemantic.Response{Receipts: receipts}, nil
		},
		requestTimeout: time.Second,
	}

	describe := getRecordUIMetadata(t, service, "/services/data/v63.0/sobjects/Order/describe")
	if describe.Code != http.StatusOK {
		t.Fatalf("describe status=%d body=%s", describe.Code, describe.Body.String())
	}
	var describeBody struct {
		RecordTypeInfos []recordTypeInfoTestWire `json:"recordTypeInfos"`
	}
	if err := json.Unmarshal(describe.Body.Bytes(), &describeBody); err != nil {
		t.Fatal(err)
	}
	if len(describeBody.RecordTypeInfos) != 2 || !hasRecordTypeInfo(describeBody.RecordTypeInfos, newID, true, true) ||
		!hasRecordTypeInfo(describeBody.RecordTypeInfos, renewalID, false, false) {
		t.Fatalf("describe record types=%#v", describeBody.RecordTypeInfos)
	}
	if hasRecordTypeInfo(describeBody.RecordTypeInfos, masterRecordTypeID, true, false) {
		t.Fatalf("describe record types=%#v, concrete object must omit Master", describeBody.RecordTypeInfos)
	}

	resolvedRecordTypes = nil
	selectedLayout := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/layout/Order?type=Create&recordTypeId="+newID)
	if selectedLayout.Code != http.StatusOK {
		t.Fatalf("selected layout status=%d body=%s", selectedLayout.Code, selectedLayout.Body.String())
	}
	if !slices.Equal(resolvedRecordTypes, []string{newID}) {
		t.Fatalf("selected layout resolved RecordTypes = %v, want %s", resolvedRecordTypes, newID)
	}
	unavailableLayout := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/layout/Order?type=Create&recordTypeId="+renewalID)
	if unavailableLayout.Code != http.StatusNotFound {
		t.Fatalf("unavailable selected layout status=%d body=%s", unavailableLayout.Code, unavailableLayout.Body.String())
	}

	resolvedRecordTypes = nil
	resolvedBatches = nil
	layouts := getRecordUIMetadata(t, service, "/services/data/v63.0/sobjects/Order/describe/layouts")
	if layouts.Code != http.StatusOK || !strings.Contains(layouts.Body.String(), `"recordTypeId":"`+newID+`"`) ||
		!strings.Contains(layouts.Body.String(), `"recordTypeId":"`+renewalID+`"`) ||
		strings.Contains(layouts.Body.String(), `"recordTypeId":"`+masterRecordTypeID+`"`) ||
		!strings.Contains(layouts.Body.String(), `"layouts":null`) ||
		!strings.Contains(layouts.Body.String(), `"recordTypeSelectorRequired":true`) {
		t.Fatalf("layouts status=%d body=%s", layouts.Code, layouts.Body.String())
	}
	expectedBatch := []string{newID, renewalID}
	sort.Strings(expectedBatch)
	if len(resolvedBatches) != 1 || !slices.Equal(resolvedBatches[0], expectedBatch) {
		t.Fatalf("layout resolution batches = %#v, want one bounded RecordType batch", resolvedBatches)
	}
	for _, test := range []struct {
		id   string
		want int
	}{{newID, http.StatusOK}, {renewalID, http.StatusNotFound}, {masterRecordTypeID, http.StatusNotFound}, {"rt_unknown", http.StatusNotFound}} {
		response := getRecordUIMetadata(t, service,
			"/services/data/v63.0/sobjects/Order/describe/layouts/"+test.id)
		if response.Code != test.want {
			t.Fatalf("layout id=%s status=%d body=%s", test.id, response.Code, response.Body.String())
		}
	}
	masterDefaults := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-defaults/create/Order?recordTypeId="+masterRecordTypeID)
	if masterDefaults.Code != http.StatusNotFound {
		t.Fatalf("Master record defaults status=%d body=%s", masterDefaults.Code, masterDefaults.Body.String())
	}
	t.Run("create defaults carry the Catalog RecordType display", func(t *testing.T) {
		defaults := getRecordUIMetadata(t, service,
			"/services/data/v63.0/ui-api/record-defaults/create/Order?recordTypeId="+newID)
		if defaults.Code != http.StatusOK {
			t.Fatalf("record defaults status=%d body=%s", defaults.Code, defaults.Body.String())
		}
		var body struct {
			Record struct {
				Fields map[string]json.RawMessage `json:"fields"`
			} `json:"record"`
		}
		if err := json.Unmarshal(defaults.Body.Bytes(), &body); err != nil {
			t.Fatal(err)
		}
		assertRecordTypeDisplayParity(t, body.Record.Fields, fixture)
	})
	t.Run("create defaults resolve the caller default RecordType before the layout", func(t *testing.T) {
		resolvedRecordTypes = nil
		defaults := getRecordUIMetadata(t, service,
			"/services/data/v63.0/ui-api/record-defaults/create/Order")
		if defaults.Code != http.StatusOK {
			t.Fatalf("record defaults status=%d body=%s", defaults.Code, defaults.Body.String())
		}
		if !slices.Equal(resolvedRecordTypes, []string{newID}) {
			t.Fatalf("resolved RecordTypes = %#v, want caller default %q", resolvedRecordTypes, newID)
		}
	})

	objectInfo := getRecordUIMetadata(t, service, "/services/data/v63.0/ui-api/object-info/Order")
	if objectInfo.Code != http.StatusOK {
		t.Fatalf("object-info status=%d body=%s", objectInfo.Code, objectInfo.Body.String())
	}
	var objectInfoBody struct {
		DefaultRecordTypeID string `json:"defaultRecordTypeId"`
		RecordTypeInfos     map[string]struct {
			Available bool `json:"available"`
			Master    bool `json:"master"`
		} `json:"recordTypeInfos"`
	}
	if err := json.Unmarshal(objectInfo.Body.Bytes(), &objectInfoBody); err != nil {
		t.Fatal(err)
	}
	if objectInfoBody.DefaultRecordTypeID != newID || len(objectInfoBody.RecordTypeInfos) != 2 ||
		!objectInfoBody.RecordTypeInfos[newID].Available || objectInfoBody.RecordTypeInfos[renewalID].Available {
		t.Fatalf("object-info record types=%#v default=%q", objectInfoBody.RecordTypeInfos, objectInfoBody.DefaultRecordTypeID)
	}

	accessReceipts := view.Receipts
	accessReceipts.CapabilityVersion = accesssemantic.CapabilityName
	client, err := NewRuntimeRecordAccessClient(func(
		context.Context, string, accesssemantic.Request,
	) (accesssemantic.Response, error) {
		return accesssemantic.Response{Results: []accesssemantic.Result{{
			RecordID: "order-1", Access: accesssemantic.Access{Read: true},
		}}, Receipts: accessReceipts}, nil
	})
	if err != nil {
		t.Fatal(err)
	}
	service.client = client
	recordReceipts := view.Receipts
	recordReceipts.CapabilityVersion = recordsemantic.CapabilityName
	service.recordView, err = NewRuntimeRecordViewClient(func(
		context.Context, string, recordsemantic.ReadRequest,
	) (recordsemantic.ReadResponse, error) {
		id, name, recordTypeID := fixture.RecordID, "Order One", fixture.RecordTypeID
		return recordsemantic.ReadResponse{
			RecordID: fixture.RecordID, Root: recordsemantic.Record{ObjectID: fixture.ObjectAPIName, Fields: []recordsemantic.Field{
				{Name: "Id", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &id}},
				{Name: "Name", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &name}},
				{Name: fixture.RecordTypeField, Value: recordsemantic.Value{Kind: recordsemantic.ValueReference, Reference: &recordTypeID}},
			}}, Access: &recordsemantic.RecordAccess{Read: true}, Receipts: recordReceipts,
		}, nil
	}, func(context.Context, string, attachmentsemantic.ResolveRequest) (attachmentsemantic.ResolveResponse, error) {
		return attachmentsemantic.ResolveResponse{}, nil
	})
	if err != nil {
		t.Fatal(err)
	}
	t.Run("record UI carries the Catalog RecordType display", func(t *testing.T) {
		recordUI := getRecordUIMetadata(t, service,
			"/services/data/v63.0/ui-api/record-ui/"+fixture.RecordID+"?type="+fixture.ObjectAPIName)
		if recordUI.Code != http.StatusOK || !strings.Contains(recordUI.Body.String(), `"defaultRecordTypeId":"`+newID+`"`) {
			t.Fatalf("record-ui status=%d body=%s", recordUI.Code, recordUI.Body.String())
		}
		var body struct {
			Records map[string]struct {
				Fields map[string]json.RawMessage `json:"fields"`
			} `json:"records"`
		}
		if err := json.Unmarshal(recordUI.Body.Bytes(), &body); err != nil {
			t.Fatal(err)
		}
		assertRecordTypeDisplayParity(t, body.Records[fixture.RecordID].Fields, fixture)
	})
}

func hasRecordTypeInfo(entries []recordTypeInfoTestWire, id string, available, defaultMapping bool) bool {
	for _, entry := range entries {
		if entry.ID == id {
			return entry.Available == available && entry.Default == defaultMapping
		}
	}
	return false
}

func TestRecordUIComposesOneRevisionBoundRuntimeView(t *testing.T) {
	type requestAuthorityKey struct{}
	authorityKey := requestAuthorityKey{}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Lead", CanRead: true, CanCreate: true, CanEdit: true, CanDelete: 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: true, CanEdit: true, CanCreate: true},
		},
	}
	service, view := describeTestService(t, permissions)
	view = installTestPageLayout(t, service, view,
		nil,
		[]byte(`{"objectApiName":"Lead","layoutName":"Lead Layout","isDefault":true,"compactLayout":["Name"],"sections":[{"id":"details","label":"Details","columns":2,"fields":["Name","OwnerId"]}],"relatedLists":[]}`),
	)
	permissions.Receipts = view.Receipts
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	authorizeCalls := 0
	service.authorizeRuntime = func(
		ctx context.Context, bearer string, capabilities []string,
	) (context.Context, error) {
		authorizeCalls++
		expected := []string{
			pagelayoutsemantic.CapabilityName,
			permissionsemantic.CapabilityName,
			recordsemantic.CapabilityName,
		}
		slices.Sort(expected)
		if bearer != "end-user" || !slices.Equal(capabilities, expected) {
			t.Fatalf("authorization bearer=%q capabilities=%#v", bearer, capabilities)
		}
		return context.WithValue(ctx, authorityKey, true), nil
	}
	service.permissionMetadata.readEffective = func(
		ctx context.Context, bearer string, request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		if ctx.Value(authorityKey) != true || bearer != "end-user" ||
			request.MinCatalogRevision == nil || *request.MinCatalogRevision != view.Receipts.CatalogRevision ||
			request.Selection == nil || !slices.Equal(request.Selection.Sections, []permissionsemantic.Section{
			permissionsemantic.SectionSystemPermissions,
			permissionsemantic.SectionCustomPermissions,
			permissionsemantic.SectionObjectPermissions,
			permissionsemantic.SectionFieldPermissions,
			permissionsemantic.SectionRecordTypePermissions,
		}) || !slices.Equal(request.Selection.ResourceTypes, []string{"Lead"}) {
			t.Fatalf("permission request=%#v bearer=%q", request, bearer)
		}
		return permissions, nil
	}
	accessCalls := 0
	client, err := NewRuntimeRecordAccessClient(func(
		ctx context.Context, bearer string, request accesssemantic.Request,
	) (accesssemantic.Response, error) {
		accessCalls++
		t.Fatalf("record-ui made a redundant record-access call: ctx=%v bearer=%q request=%#v", ctx, bearer, request)
		return accesssemantic.Response{}, nil
	})
	if err != nil {
		t.Fatal(err)
	}
	service.client = client
	recordReceipts := view.Receipts
	recordReceipts.CapabilityVersion = recordsemantic.CapabilityName
	readCalls := 0
	recordView, err := NewRuntimeRecordViewClient(func(
		ctx context.Context, bearer string, request recordsemantic.ReadRequest,
	) (recordsemantic.ReadResponse, error) {
		readCalls++
		if ctx.Value(authorityKey) != true {
			t.Fatal("record-view call did not inherit the request-scoped Runtime authority")
		}
		if bearer != "end-user" || request.ObjectID != "Lead" || request.Selector.RecordID != "lead-1" ||
			request.MinCatalogRevision == nil || *request.MinCatalogRevision != view.Receipts.CatalogRevision ||
			request.Authority != nil {
			t.Fatalf("record request = %#v bearer=%q", request, bearer)
		}
		id, name, owner := "lead-1", "Example Lead", "user-1"
		return recordsemantic.ReadResponse{
			RecordID: "lead-1",
			Root: recordsemantic.Record{ObjectID: "Lead", Fields: []recordsemantic.Field{
				{Name: "Id", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &id}},
				{Name: "Name", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &name}},
				{Name: "OwnerId", Value: recordsemantic.Value{Kind: recordsemantic.ValueReference, Reference: &owner}},
			}},
			ReferenceDisplays: []recordsemantic.ReferenceDisplay{{
				SourceFieldID: "OwnerId", RelationshipID: "Owner", TargetObjectID: "User",
				TargetRecordID: "user-1", DisplayName: "Ada Example",
			}},
			Access:   &recordsemantic.RecordAccess{Read: true, Update: true, Delete: true},
			Receipts: recordReceipts,
		}, nil
	}, func(context.Context, string, attachmentsemantic.ResolveRequest) (attachmentsemantic.ResolveResponse, error) {
		t.Fatal("record-ui unexpectedly resolved attachment sources")
		return attachmentsemantic.ResolveResponse{}, nil
	})
	if err != nil {
		t.Fatal(err)
	}
	service.recordView = recordView
	service.requestTimeout = time.Second

	request := httptest.NewRequest(http.MethodGet,
		"/services/data/v63.0/ui-api/record-ui/lead-1?type=Lead", nil)
	request.Header.Set("Authorization", "Bearer end-user")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusOK || authorizeCalls != 1 || accessCalls != 0 || readCalls != 1 {
		t.Fatalf("status=%d authorize=%d access=%d read=%d body=%s", response.Code, authorizeCalls, accessCalls, readCalls, response.Body.String())
	}
	var body struct {
		Records map[string]struct {
			APIName      string                     `json:"apiName"`
			RecordAccess map[string]bool            `json:"recordAccess"`
			Fields       map[string]json.RawMessage `json:"fields"`
		} `json:"records"`
		Layouts     map[string]map[string]json.RawMessage `json:"layouts"`
		ObjectInfos map[string]json.RawMessage            `json:"objectInfos"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	record := body.Records["lead-1"]
	if record.APIName != "Lead" || !record.RecordAccess["hasReadAccess"] ||
		len(record.Fields) != 4 || len(body.Layouts["Lead"]) != 1 || len(body.ObjectInfos["Lead"]) == 0 {
		t.Fatalf("record-ui = %#v", body)
	}
}

func TestRecordUIRetainsAuthorizedRelatedListsFromL1Layout(t *testing.T) {
	service := relatedListsRecordUITestService(t, false)
	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-ui/parent-1?type=Parent")
	if response.Code != http.StatusOK {
		t.Fatalf("record-ui status=%d body=%s", response.Code, response.Body.String())
	}
	var body struct {
		Layouts map[string]map[string]struct {
			RelatedLists []struct {
				ObjectAPIName string   `json:"objectApiName"`
				LookupField   string   `json:"lookupField"`
				Fields        []string `json:"fields"`
			} `json:"relatedLists"`
		} `json:"layouts"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	related := body.Layouts["Parent"]["Parent-default-layout"].RelatedLists
	if len(related) != 1 || related[0].ObjectAPIName != "Child" ||
		related[0].LookupField != "ParentId" ||
		!slices.Equal(related[0].Fields, []string{"Name"}) {
		t.Fatalf("record-ui related lists=%#v", related)
	}
}

func virtualHistoryRecordUITestService(t *testing.T) *Service {
	t.Helper()
	return relatedListsRecordUITestService(t, true)
}

func relatedListsRecordUITestService(t *testing.T, includeHistory bool) *Service {
	t.Helper()
	searchable := true
	standardObject := "standard_object"
	systemField := true
	appField := false
	view := createInputSearchTestView(t, []searchObjectSpec{
		{
			APIName: "Parent", Label: "Parent", PluralLabel: "Parents", KeyPrefix: "a01",
			TableType: &standardObject, Searchable: &searchable, CreateRequiredFields: &[]string{},
			Fields: []searchFieldSpec{
				{APIName: "Id", Label: "Parent ID", Type: "string", IsSystem: &systemField},
				{APIName: "Name", Label: "Parent Name", Type: "string", IsNameField: true, IsSystem: &appField, Createable: true, Updateable: true},
			},
		},
		{
			APIName: "Child", Label: "Child", PluralLabel: "Children", KeyPrefix: "a02",
			TableType: &standardObject, Searchable: &searchable, CreateRequiredFields: &[]string{},
			ListFields: []string{"Name"},
			Fields: []searchFieldSpec{
				{APIName: "Id", Label: "Child ID", Type: "string", IsSystem: &systemField},
				{APIName: "Name", Label: "Child Name", Type: "string", IsNameField: true, IsSystem: &appField, Createable: true, Updateable: true},
				{APIName: "ParentId", Label: "Parent", Type: "reference", IsSystem: &appField,
					ReferenceTo: []string{"Parent"}, ChildRelationshipName: "Children"},
			},
		},
	})
	component := flowActionsTestComponent(
		t, appPageLayoutComponentKind, "Parent/default",
		`{"objectApiName":"Parent","layoutName":"Parent Layout","isDefault":true,"compactLayout":["Name"],"sections":[{"id":"details","label":"Details","columns":1,"fields":["Name"]}],"relatedLists":[{"id":"Children","label":"Children","objectApiName":"Child","lookupField":"ParentId","fields":[]},{"id":"History","label":"History","objectApiName":"ParentHistory","lookupField":"ParentId","fields":["Field","OldValue","NewValue","CreatedDate","CreatedById"]}]}`,
	)
	if !includeHistory {
		var layout appPageLayout
		if err := json.Unmarshal(component.CanonicalBehavior, &layout); err != nil {
			t.Fatal(err)
		}
		layout.RelatedLists = layout.RelatedLists[:1]
		raw, err := json.Marshal(layout)
		if err != nil {
			t.Fatal(err)
		}
		component = flowActionsTestComponent(t, appPageLayoutComponentKind, "Parent/default", string(raw))
	}
	view.AppComponents = append(view.AppComponents, component)

	sort.Slice(view.AppComponents, func(left, right int) bool {
		return view.AppComponents[left].ComponentID < view.AppComponents[right].ComponentID
	})
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
	if err := catalogsemantic.ValidateAppMetadataViewV3(view); err != nil {
		t.Fatalf("record-ui related-list view is invalid: %v", err)
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Child", CanRead: true},
			{ResourceType: "Parent", CanRead: true, CanCreate: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Child", FieldName: "Id", CanRead: true},
			{ResourceType: "Child", FieldName: "Name", CanRead: true},
			{ResourceType: "Child", FieldName: "ParentId", CanRead: true},
			{ResourceType: "Parent", FieldName: "Id", CanRead: true},
			{ResourceType: "Parent", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
		},
		Receipts: view.Receipts,
	}
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	service := &Service{
		resolvePageLayoutRuntime:    testPageLayoutResolver(t, view),
		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.Selection == nil || (!slices.Equal(request.Selection.ResourceTypes, []string{"Child", "Parent"}) && !slices.Equal(request.Selection.ResourceTypes, []string{"Parent"})) {
				t.Fatalf("record-ui permission selection=%#v", request.Selection)
			}
			return selectedPermissionTestResponse(permissions, request), nil
		}},
		requestTimeout: time.Second,
	}
	recordReceipts := view.Receipts
	recordReceipts.CapabilityVersion = recordsemantic.CapabilityName
	var err error
	service.recordView, err = NewRuntimeRecordViewClient(func(
		_ context.Context, _ string, request recordsemantic.ReadRequest,
	) (recordsemantic.ReadResponse, error) {
		if request.Authority != nil {
			t.Fatalf("record-ui widened root-only RecordView authority: %#v", request.Authority)
		}
		id, name := "parent-1", "Example Parent"
		return recordsemantic.ReadResponse{
			RecordID: id,
			Root: recordsemantic.Record{ObjectID: "Parent", Fields: []recordsemantic.Field{
				{Name: "Id", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &id}},
				{Name: "Name", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &name}},
			}},
			Access: &recordsemantic.RecordAccess{Read: true}, Receipts: recordReceipts,
		}, nil
	}, func(context.Context, string, attachmentsemantic.ResolveRequest) (attachmentsemantic.ResolveResponse, error) {
		return attachmentsemantic.ResolveResponse{}, nil
	})
	if err != nil {
		t.Fatal(err)
	}

	service.previewCreateDefaults = func(_ context.Context, _ string, _ defaultsemantic.Request) (defaultsemantic.Response, error) {
		receipts := view.Receipts
		receipts.CapabilityVersion = defaultsemantic.CapabilityName
		return defaultsemantic.Response{Receipts: receipts}, nil
	}
	return service
}

func TestRecordUIRetainsVirtualHistoryAlongsideCurrentPhysicalListFields(t *testing.T) {
	service := virtualHistoryRecordUITestService(t)
	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-ui/parent-1?type=Parent")
	if response.Code != http.StatusOK {
		t.Fatalf("record-ui status=%d body=%s", response.Code, response.Body.String())
	}
	var body struct {
		Layouts map[string]map[string]struct {
			RelatedLists []struct {
				ObjectAPIName string   `json:"objectApiName"`
				LookupField   string   `json:"lookupField"`
				Fields        []string `json:"fields"`
			} `json:"relatedLists"`
		} `json:"layouts"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	related := body.Layouts["Parent"]["Parent-default-layout"].RelatedLists
	if len(related) != 2 || related[0].ObjectAPIName != "Child" ||
		related[0].LookupField != "ParentId" ||
		!slices.Equal(related[0].Fields, []string{"Name"}) {
		t.Fatalf("record-ui related lists=%#v", related)
	}
	if len(related) == 2 && (related[1].ObjectAPIName != "ParentHistory" || !slices.Equal(related[1].Fields, []string{"Field", "OldValue", "NewValue", "CreatedDate", "CreatedById"})) {
		t.Fatalf("history related list=%#v", related[1])
	}
}

func TestRecordUIFailsClosedOnMixedCatalogRevision(t *testing.T) {
	service, view := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
	})
	view = installTestPageLayout(t, service, view,
		[]byte(`{"objectApiName":"Lead","layoutName":"Lead Layout","isDefault":true,"compactLayout":[],"sections":[],"relatedLists":[]}`), nil)
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
		Receipts:          view.Receipts,
	}
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	service.permissionMetadata.readEffective = func(context.Context, string, permissionsemantic.Request) (permissionsemantic.Response, error) {
		return permissions, nil
	}
	mixed := view.Receipts
	mixed.CatalogRevision++
	mixed.CapabilityVersion = recordsemantic.CapabilityName
	var err error
	service.recordView, err = NewRuntimeRecordViewClient(
		func(context.Context, string, recordsemantic.ReadRequest) (recordsemantic.ReadResponse, error) {
			id := "lead-1"
			return recordsemantic.ReadResponse{
				RecordID: id, Root: recordsemantic.Record{ObjectID: "Lead", Fields: []recordsemantic.Field{{
					Name: "Id", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &id},
				}}}, Access: &recordsemantic.RecordAccess{Read: true}, Receipts: mixed,
			}, nil
		},
		func(context.Context, string, attachmentsemantic.ResolveRequest) (attachmentsemantic.ResolveResponse, error) {
			return attachmentsemantic.ResolveResponse{}, nil
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	service.requestTimeout = time.Second
	request := httptest.NewRequest(http.MethodGet,
		"/services/data/v63.0/ui-api/record-ui/lead-1?type=Lead", nil)
	request.Header.Set("Authorization", "Bearer end-user")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusServiceUnavailable {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestRecordUIFailsClosedOnMixedSecurityEpochAfterReconciliation(t *testing.T) {
	service, view := describeTestService(t, permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
	})
	view = installTestPageLayout(t, service, view,
		[]byte(`{"objectApiName":"Lead","layoutName":"Lead Layout","isDefault":true,"compactLayout":[],"sections":[],"relatedLists":[]}`), nil)
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Lead", CanRead: true}},
		Receipts:          view.Receipts,
	}
	permissions.Receipts.SecurityEpoch++
	permissions.Receipts.CapabilityVersion = permissionsemantic.CapabilityName
	service.permissionMetadata.readEffective = func(context.Context, string, permissionsemantic.Request) (permissionsemantic.Response, error) {
		return permissions, nil
	}
	service.reconcileAppMetadataV3 = func(
		_ context.Context, expected commonsemantic.Receipts, actual commonsemantic.Receipts,
	) (bool, error) {
		if expected.SecurityEpoch+1 != actual.SecurityEpoch {
			t.Fatalf("reconcile expected=%#v actual=%#v", expected, actual)
		}
		return true, nil
	}
	recordReceipts := view.Receipts
	recordReceipts.CapabilityVersion = recordsemantic.CapabilityName
	var err error
	service.recordView, err = NewRuntimeRecordViewClient(
		func(context.Context, string, recordsemantic.ReadRequest) (recordsemantic.ReadResponse, error) {
			id := "lead-1"
			secret := "must-not-be-projected"
			return recordsemantic.ReadResponse{
				RecordID: id, Root: recordsemantic.Record{ObjectID: "Lead", Fields: []recordsemantic.Field{
					{Name: "Id", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &id}},
					{Name: "Secret__c", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &secret}},
				}}, Access: &recordsemantic.RecordAccess{Read: true}, Receipts: recordReceipts,
			}, nil
		},
		func(context.Context, string, attachmentsemantic.ResolveRequest) (attachmentsemantic.ResolveResponse, error) {
			return attachmentsemantic.ResolveResponse{}, nil
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	service.requestTimeout = time.Second
	response := getRecordUIMetadata(t, service,
		"/services/data/v63.0/ui-api/record-ui/lead-1?type=Lead")
	if response.Code != http.StatusServiceUnavailable ||
		strings.Contains(response.Body.String(), "must-not-be-projected") {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestRecordUIAppliesAttachmentDisclosureProjection(t *testing.T) {
	raw := "https://example.invalid/sfc/p/00D/a/BBBBBBBBBBBB/secret"
	read := recordsemantic.ReadResponse{
		RecordID: "record-1",
		Root: recordsemantic.Record{ObjectID: "Account", Fields: []recordsemantic.Field{{
			Name: "DocumentLink", Value: recordsemantic.Value{Kind: recordsemantic.ValueString, String: &raw},
		}}},
		AttachmentSourceFields: []recordsemantic.AttachmentSourceField{{
			SourceFieldID: "DocumentLink", ResolverID: "legacy_attachment",
		}},
		Receipts: recordViewReceipts(),
	}
	client, err := NewRuntimeRecordViewClient(
		func(context.Context, string, recordsemantic.ReadRequest) (recordsemantic.ReadResponse, error) {
			return recordsemantic.ReadResponse{}, nil
		},
		func(_ context.Context, bearer string, request attachmentsemantic.ResolveRequest) (attachmentsemantic.ResolveResponse, error) {
			if bearer != "end-user" || request.ParentObjectID != "Account" || request.ParentRecordID != "record-1" ||
				request.MinCatalogRevision == nil || *request.MinCatalogRevision != read.Receipts.CatalogRevision {
				t.Fatalf("attachment request = %#v bearer=%q", request, bearer)
			}
			return attachmentsemantic.ResolveResponse{}, errors.New("resolver unavailable")
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	decorateRecordViewReadAttachments(ctx, client, "end-user", recordViewRoute{
		version: "v63.0", objectID: "Account", recordID: "record-1",
	}, &read)
	projected, err := projectRecordUIRecord(
		"Account", read, RecordAccess{HasReadAccess: true}, testMasterRecordTypeProjection(),
	)
	if err != nil {
		t.Fatal(err)
	}
	fields := projected["fields"].(map[string]any)
	link := fields["DocumentLink"].(map[string]any)
	if link["value"] != "File unavailable" {
		t.Fatalf("record-ui attachment value = %#v", link["value"])
	}
}

func TestRecordUIObjectInfoDoesNotDiscloseUnreadableReferenceTarget(t *testing.T) {
	spec := searchObjectSpec{APIName: "Account", Label: "Account", Fields: []searchFieldSpec{{
		APIName: "OwnerId", Label: "Owner", Type: "reference", ReferenceTo: []string{"User"},
		RelationshipName: "Owner",
	}}}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Account", CanRead: true}},
		FieldPermissions:  []permissionsemantic.FieldPermission{{ResourceType: "Account", FieldName: "OwnerId", CanRead: true}},
	}
	projected := projectRecordUIObjectInfo(spec, permissions.ObjectPermissions[0], permissions, testMasterRecordTypeProjection(), spec.ListFields)
	fields := projected["fields"].(map[string]any)
	owner := fields["OwnerId"].(map[string]any)
	if references := owner["referenceToInfos"].([]map[string]any); len(references) != 0 {
		t.Fatalf("unreadable reference targets = %#v, want none", references)
	}
	if relationshipName := owner["relationshipName"]; relationshipName != "" {
		t.Fatalf("unreadable relationshipName = %#v, want empty", relationshipName)
	}
	if reference := owner["reference"]; reference != false {
		t.Fatalf("unreadable reference marker = %#v, want false", reference)
	}
}

func TestVirtualHistoryLayoutsAcrossHTTPConsumers(t *testing.T) {
	for _, path := range []string{
		"/services/data/v63.0/ui-api/layout/Parent?type=View",
		"/services/data/v63.0/ui-api/layout/Parent?type=Create",
		"/services/data/v63.0/sobjects/Parent/describe/layouts",
		"/services/data/v63.0/ui-api/record-defaults/create/Parent",
		"/services/data/v63.0/ui-api/record-defaults/clone/parent-1?type=Parent",
	} {
		t.Run(path, func(t *testing.T) {
			service := virtualHistoryRecordUITestService(t)
			reader := service.readObjectListFieldsRuntime
			calls := 0
			service.readObjectListFieldsRuntime = func(ctx context.Context, bearer string, request pagelayoutsemantic.ObjectListFieldsRequest) (pagelayoutsemantic.ObjectListFieldsResponse, error) {
				calls++
				if !slices.Equal(request.ObjectIDs, []string{"Child", "Parent"}) {
					t.Fatalf("physical Runtime batch=%v", request.ObjectIDs)
				}
				return reader(ctx, bearer, request)
			}
			response := getRecordUIMetadata(t, service, path)
			if response.Code != http.StatusOK {
				t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
			}
			if calls == 0 {
				t.Fatal("HTTP entrypoint bypassed current Runtime list fields")
			}
		})
	}
}

func TestVirtualHistoryLayoutKeepsLiveColumnsAndFailsClosed(t *testing.T) {
	for _, tc := range []struct {
		name                                         string
		denyFields, denyChild, stale, invalidHistory bool
		wantStatus                                   int
	}{
		{name: "current ordinary columns", wantStatus: 200},
		{name: "parent fields denied", denyFields: true, wantStatus: 200},
		{name: "child read denied", denyChild: true, wantStatus: 200},
		{name: "mixed revision", stale: true, wantStatus: 503},
		{name: "invalid history object", invalidHistory: true, wantStatus: 404},
	} {
		t.Run(tc.name, func(t *testing.T) {
			service := virtualHistoryRecordUITestService(t)
			permissionReader := service.permissionMetadata.readEffective
			service.permissionMetadata.readEffective = func(ctx context.Context, bearer string, request permissionsemantic.Request) (permissionsemantic.Response, error) {
				response, err := permissionReader(ctx, bearer, request)
				for i := range response.FieldPermissions {
					if tc.denyFields && response.FieldPermissions[i].ResourceType == "Parent" {
						response.FieldPermissions[i].CanRead = false
					}
				}
				for i := range response.ObjectPermissions {
					if tc.denyChild && response.ObjectPermissions[i].ResourceType == "Child" {
						response.ObjectPermissions[i].CanRead = false
					}
				}
				return response, err
			}
			resolver := service.resolvePageLayoutRuntime
			service.resolvePageLayoutRuntime = func(ctx context.Context, bearer string, request pagelayoutsemantic.Request) (pagelayoutsemantic.Response, error) {
				response, err := resolver(ctx, bearer, request)
				if tc.invalidHistory {
					for i := range response.Layouts {
						response.Layouts[i].LayoutDocument = []byte(strings.ReplaceAll(string(response.Layouts[i].LayoutDocument), "ParentHistory", "MissingHistory"))
					}
				}
				return response, err
			}
			reader := service.readObjectListFieldsRuntime
			service.readObjectListFieldsRuntime = func(ctx context.Context, bearer string, request pagelayoutsemantic.ObjectListFieldsRequest) (pagelayoutsemantic.ObjectListFieldsResponse, error) {
				response, err := reader(ctx, bearer, request)
				if err != nil {
					return response, &pagelayoutsemantic.Failure{Category: pagelayoutsemantic.FailureMissing}
				}
				for i := range response.Objects {
					if response.Objects[i].ObjectID == "Child" {
						response.Objects[i].OrderedFieldIDs = []string{"Id", "Name"}
					}
				}
				if tc.stale {
					response.Receipts.CatalogRevision++
				}
				return response, nil
			}
			response := getRecordUIMetadata(t, service, "/services/data/v63.0/ui-api/record-ui/parent-1?type=Parent")
			if response.Code != tc.wantStatus {
				t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
			}
			if tc.wantStatus != 200 {
				return
			}
			var body struct {
				Layouts map[string]map[string]struct {
					RelatedLists []appRelatedList `json:"relatedLists"`
				} `json:"layouts"`
			}
			if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
				t.Fatal(err)
			}
			related := body.Layouts["Parent"]["Parent-default-layout"].RelatedLists
			expectedCount := 2
			if tc.denyChild {
				expectedCount = 1
			}
			if len(related) != expectedCount {
				t.Fatalf("related lists=%#v", related)
			}
			if !tc.denyChild && !slices.Equal(related[0].Fields, []string{"Id", "Name"}) {
				t.Fatalf("current ordinary fields not used: %v", related[0].Fields)
			}
			history := related[len(related)-1]
			want := []string{"Field", "OldValue", "NewValue", "CreatedDate", "CreatedById"}
			if tc.denyFields {
				want = []string{"Field", "CreatedDate", "CreatedById"}
			}
			if history.ObjectAPIName != "ParentHistory" || !slices.Equal(history.Fields, want) {
				t.Fatalf("history=%#v", history)
			}
		})
	}
}
