package runtimegrpc

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"math/big"
	"net"
	"slices"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	identitysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/identityv1"
	querysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	semantic "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"
	viewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordviewv1"
	recyclemutationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recyclebinmutationv1"
	recyclesemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recyclebinv1"
	relatedsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/relatedcountsv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

type runtimeClientTestServer struct {
	runtimev1.UnimplementedIdentityContextServiceServer
	runtimev1.UnimplementedRecordsServiceServer
	runtimev1.UnimplementedRecordCreateDefaultPreviewServiceServer
	runtimev1.UnimplementedRecordQueryServiceServer
	runtimev1.UnimplementedRelatedRecordCountServiceServer
	runtimev1.UnimplementedRecycleBinReadServiceServer
	runtimev1.UnimplementedRecycleBinMutationServiceServer
	runtimev1.UnimplementedRecordViewReadServiceServer
	runtimev1.UnimplementedRecordAttachmentSourceResolutionServiceServer
	runtimev1.UnimplementedRecordMutationsServiceServer
	identityCalls, recordsCalls atomic.Uint64
	legacyAttachmentCalls       atomic.Uint64
	recordsDeadlineRemaining    atomic.Int64
	rejectIdentity              bool
	identityDelay               time.Duration
	maximumRecordLease          time.Duration
	expectedIdentityCapability  string
	identityCapabilityMu        sync.Mutex
	identityCapabilities        []string
	t                           *testing.T
}

func (s *runtimeClientTestServer) Exchange(ctx context.Context, request *runtimev1.ExchangeRequest) (*runtimev1.ExchangeResponse, error) {
	s.identityCalls.Add(1)
	values, _ := metadata.FromIncomingContext(ctx)
	if got := values.Get("authorization"); len(got) != 1 || got[0] != "Bearer workload-secret" {
		s.t.Errorf("workload authorization = %#v", got)
	}
	if got := values.Get("x-nexuscrm-end-user-authorization"); len(got) != 1 || got[0] != "Bearer user-secret" {
		s.t.Errorf("end-user authorization = %#v", got)
	}
	if s.expectedIdentityCapability != "" && (request == nil || len(request.GetRequestedRuntimeCapabilities()) != 1 || request.GetRequestedRuntimeCapabilities()[0] != s.expectedIdentityCapability) {
		s.t.Errorf("requested capabilities = %#v, want %q", request.GetRequestedRuntimeCapabilities(), s.expectedIdentityCapability)
	}
	s.identityCapabilityMu.Lock()
	if request != nil {
		s.identityCapabilities = append(s.identityCapabilities, request.GetRequestedRuntimeCapabilities()...)
	}
	s.identityCapabilityMu.Unlock()
	if s.rejectIdentity {
		return nil, status.Error(codes.Unauthenticated, "rejected")
	}
	if s.identityDelay > 0 {
		select {
		case <-time.After(s.identityDelay):
		case <-ctx.Done():
			return nil, status.FromContextError(ctx.Err()).Err()
		}
	}
	return &runtimev1.ExchangeResponse{OpaqueDelegation: "header.payload.signature", ExpiresAtUnixSecond: time.Now().Add(time.Minute).Unix()}, nil
}

func (s *runtimeClientTestServer) QueryRecords(ctx context.Context, request *runtimev1.QueryRecordsRequest) (*runtimev1.QueryRecordsResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireQueryRecordsRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToQueryStatusError(err)
	}
	value := uint64(0)
	return mapping.ToWireQueryRecordsResponse(querysemantic.Response{
		TotalSize: 1, Done: true,
		Records:  []querysemantic.Record{{ObjectID: semanticRequest.ObjectID, Fields: []querysemantic.Field{{Name: "RetryCount", Value: querysemantic.Value{Kind: querysemantic.ValueUnsigned, Unsigned: &value}}}}},
		Receipts: querysemantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: querysemantic.CapabilityName, CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1},
	})
}

func (s *runtimeClientTestServer) QueryHistory(ctx context.Context, request *runtimev1.QueryHistoryRequest) (*runtimev1.QueryHistoryResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireQueryHistoryRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToQueryStatusError(err)
	}
	return mapping.ToWireQueryHistoryResponse(querysemantic.HistoryResponse{
		TotalSize: 1, Done: true,
		Rows: []querysemantic.HistoryRow{{
			ID: "history-1", ParentID: semanticRequest.ParentRecordID, Field: "Status", DataType: "string",
			CreatedDate: "2026-07-19T00:00:00Z", CreatedByID: "user-1",
		}},
		Receipts: querysemantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: querysemantic.CapabilityName, CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1},
	})
}

func (s *runtimeClientTestServer) CountRelatedRecords(ctx context.Context, request *runtimev1.CountRelatedRecordsRequest) (*runtimev1.CountRelatedRecordsResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireCountRelatedRecordsRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRelatedCountStatusError(err)
	}
	results := make([]relatedsemantic.Result, len(semanticRequest.Counts))
	for index := range results {
		results[index] = relatedsemantic.Result{Index: uint32(index), Outcome: relatedsemantic.CountOutcomeCounted, Count: uint64(index)}
	}
	return mapping.ToWireCountRelatedRecordsResponse(semanticRequest, relatedsemantic.Response{
		Results:  results,
		Receipts: relatedsemantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: relatedsemantic.CapabilityName, CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1},
	})
}

func (s *runtimeClientTestServer) PreviewCreateDefaults(
	ctx context.Context,
	request *runtimev1.PreviewCreateDefaultsRequest,
) (*runtimev1.PreviewCreateDefaultsResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWirePreviewCreateDefaultsRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToCreateDefaultPreviewStatusError(err)
	}
	if semanticRequest.ObjectID != "RankedChild" {
		return nil, mapping.ToCreateDefaultPreviewStatusError(&defaultsemantic.Failure{
			Category:         defaultsemantic.FailureInvalidRequest,
			InvalidComponent: defaultsemantic.InvalidComponentObjectID,
		})
	}
	next := int64(901)
	return mapping.ToWirePreviewCreateDefaultsResponse(semanticRequest, defaultsemantic.Response{
		Fields: []defaultsemantic.Field{{
			Name: "Sequence", Kind: defaultsemantic.ValueInteger,
			Integer: &next, Provenance: defaultsemantic.ProvenanceSiblingMaxPlusOne,
		}},
		Receipts: defaultsemantic.Receipts{
			ProtocolVersion: "1.0", CapabilityVersion: defaultsemantic.CapabilityName,
			CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1,
		},
	})
}

func (s *runtimeClientTestServer) ListRecycleBin(ctx context.Context, request *runtimev1.ListRecycleBinRequest) (*runtimev1.ListRecycleBinResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireListRecycleBinRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRecycleBinStatusError(err)
	}
	if semanticRequest.Scope != recyclesemantic.ScopeMine {
		return nil, mapping.ToRecycleBinStatusError(&recyclesemantic.Failure{
			Category: recyclesemantic.FailureInvalidRequest, InvalidComponent: recyclesemantic.InvalidComponentOperation,
		})
	}
	return mapping.ToWireListRecycleBinResponse(recyclesemantic.Response{
		Items: []recyclesemantic.Item{{
			RecordID: "row-1", ObjectID: "WorkItem", DeletedDate: "2026-07-22T04:00:00Z",
		}},
		Receipts: recyclesemantic.Receipts{
			ProtocolVersion: "1.0", CapabilityVersion: recyclesemantic.CapabilityName,
			CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1,
		},
	})
}

func (s *runtimeClientTestServer) RestoreRecycleBin(ctx context.Context, request *runtimev1.RestoreRecycleBinRequest) (*runtimev1.RestoreRecycleBinResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireRestoreRecycleBinRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRecycleBinMutationStatusError(err)
	}
	return mapping.ToWireRestoreRecycleBinResponse(semanticRequest, recyclemutationsemantic.Response{Receipts: recyclemutationsemantic.Receipts{
		ProtocolVersion: "1.0", CapabilityVersion: recyclemutationsemantic.CapabilityName,
		CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1,
	}})
}

func (s *runtimeClientTestServer) PurgeRecycleBin(ctx context.Context, request *runtimev1.PurgeRecycleBinRequest) (*runtimev1.PurgeRecycleBinResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWirePurgeRecycleBinRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRecycleBinMutationStatusError(err)
	}
	return mapping.ToWirePurgeRecycleBinResponse(semanticRequest, recyclemutationsemantic.Response{Receipts: recyclemutationsemantic.Receipts{
		ProtocolVersion: "1.0", CapabilityVersion: recyclemutationsemantic.CapabilityName,
		CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1,
	}})
}

func (s *runtimeClientTestServer) ReadRecordView(ctx context.Context, request *runtimev1.ReadRecordViewRequest) (*runtimev1.ReadRecordViewResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireReadRecordViewRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRecordViewStatusError(err)
	}
	value := semanticRequest.Selector.RecordID
	return mapping.ToWireReadRecordViewResponse(semanticRequest, viewsemantic.ReadResponse{
		RecordID: value,
		Root: viewsemantic.Record{ObjectID: semanticRequest.ObjectID, Fields: []viewsemantic.Field{{
			Name: "Id", Value: viewsemantic.Value{Kind: viewsemantic.ValueString, String: &value},
		}}},
		Receipts: viewsemantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: viewsemantic.CapabilityName, CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1},
	})
}

func (s *runtimeClientTestServer) ResolveRecordAttachmentSources(ctx context.Context, request *runtimev1.ResolveRecordAttachmentSourcesRequest) (*runtimev1.ResolveRecordAttachmentSourcesResponse, error) {
	s.legacyAttachmentCalls.Add(1)
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireResolveRecordAttachmentSourcesRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRecordViewStatusError(err)
	}
	results := make([]viewsemantic.AttachmentSourceResult, len(semanticRequest.SourceValues))
	for index := range results {
		results[index] = viewsemantic.AttachmentSourceResult{Index: uint32(index), Outcome: viewsemantic.AttachmentSourceNotFound}
	}
	return mapping.ToWireResolveRecordAttachmentSourcesResponse(semanticRequest, viewsemantic.ResolveAttachmentSourcesResponse{
		Results:  results,
		Receipts: viewsemantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: viewsemantic.CapabilityName, CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1},
	})
}

func (s *runtimeClientTestServer) ResolveAttachmentSources(ctx context.Context, request *runtimev1.ResolveAttachmentSourcesRequest) (*runtimev1.ResolveAttachmentSourcesResponse, error) {
	s.recordsCalls.Add(1)
	s.assertDataRPCMetadata(ctx)
	semanticRequest, err := mapping.FromWireResolveAttachmentSourcesRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRecordAttachmentSourceStatusError(err)
	}
	results := make([]attachmentsemantic.SourceResult, len(semanticRequest.Sources))
	for index := range results {
		results[index] = attachmentsemantic.SourceResult{Index: uint32(index), Outcome: attachmentsemantic.SourceNotFound}
	}
	return mapping.ToWireResolveAttachmentSourcesResponse(semanticRequest, attachmentsemantic.ResolveResponse{
		Results: results,
		Receipts: attachmentsemantic.Receipts{
			ProtocolVersion: "1.0", CapabilityVersion: attachmentsemantic.CapabilityName,
			CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1,
		},
	})
}

func (s *runtimeClientTestServer) requestedIdentityCapabilities() []string {
	s.identityCapabilityMu.Lock()
	defer s.identityCapabilityMu.Unlock()
	return append([]string(nil), s.identityCapabilities...)
}

func (s *runtimeClientTestServer) assertDataRPCMetadata(ctx context.Context) {
	s.t.Helper()
	values, _ := metadata.FromIncomingContext(ctx)
	if got := values.Get(EndUserAuthorizationMetadataKey); len(got) != 0 {
		s.t.Errorf("Runtime data RPC received end-user authorization = %#v", got)
	}
	if got := values.Get(envelope.RuntimeDelegationMetadataKey); len(got) != 1 || got[0] != "header.payload.signature" {
		s.t.Errorf("Runtime delegation = %#v", got)
	}
}

func (s *runtimeClientTestServer) EvaluateRecordAccess(ctx context.Context, request *runtimev1.EvaluateRecordAccessRequest) (*runtimev1.EvaluateRecordAccessResponse, error) {
	s.recordsCalls.Add(1)
	deadline, ok := ctx.Deadline()
	if !ok {
		return nil, status.Error(codes.InvalidArgument, "deadline is required")
	}
	remaining := time.Until(deadline)
	s.recordsDeadlineRemaining.Store(int64(remaining))
	if s.maximumRecordLease > 0 && remaining > s.maximumRecordLease {
		return nil, status.Error(codes.ResourceExhausted, "record-access admission lease exceeded")
	}
	values, _ := metadata.FromIncomingContext(ctx)
	if got := values.Get(EndUserAuthorizationMetadataKey); len(got) != 0 {
		s.t.Errorf("Records RPC received end-user authorization = %#v", got)
	}
	if got := values.Get(envelope.RuntimeDelegationMetadataKey); len(got) != 1 || got[0] != "header.payload.signature" {
		s.t.Errorf("Runtime delegation = %#v", got)
	}
	semanticRequest, err := mapping.FromWireEvaluateRecordAccessRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToRecordAccessStatusError(err)
	}
	return mapping.ToWireEvaluateRecordAccessResponse(semanticRequest, semantic.Response{
		Results:  []semantic.Result{{RecordID: "ISS-1", Access: semantic.Access{Read: true}}},
		Receipts: semantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName, CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 1},
	})
}

func TestRecordAccessRuntimeOwnsIdentityThenExactlyOneDataRPC(t *testing.T) {
	for _, reject := range []bool{false, true} {
		t.Run(map[bool]string{false: "success", true: "identity rejection"}[reject], func(t *testing.T) {
			target, server, transport, cleanup := startRuntimeClientTestServer(t, reject)
			defer cleanup()
			ctx, cancel := context.WithTimeout(metadata.NewOutgoingContext(context.Background(), metadata.Pairs(
				EndUserAuthorizationMetadataKey, "Bearer stale-user-secret",
			)), 2*time.Second)
			defer cancel()
			client, err := dialRecordAccessRuntimeWithCredentials(ctx, RuntimeClientConfig{
				Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
			}, transport)
			if err != nil {
				t.Fatal(err)
			}
			defer client.Close()
			object := "issue"
			response, err := client.Evaluate(ctx, "user-secret", semantic.Request{ObjectID: &object, RecordIDs: []string{"ISS-1"}})
			if reject {
				var failure *identitysemantic.Failure
				if !errors.As(err, &failure) || failure.Category != identitysemantic.FailureUnauthenticated || server.recordsCalls.Load() != 0 {
					t.Fatalf("error=%v records=%d", err, server.recordsCalls.Load())
				}
				return
			}
			if err != nil || len(response.Results) != 1 || server.identityCalls.Load() != 1 || server.recordsCalls.Load() != 1 ||
				client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 1, RecordsCalls: 1}) {
				t.Fatalf("response=%#v err=%v calls=%d/%d metrics=%#v", response, err, server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics())
			}
		})
	}
}

func TestRecordAccessRuntimeReusesRequestScopedDelegation(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRecordAccessRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()

	object := "issue"
	response, err := client.Evaluate(
		withRequestScopedRuntimeDelegation(
			ctx, "header.payload.signature", client.identityCoordinator, client.appInstallationID,
		),
		"must-not-be-exchanged",
		semantic.Request{ObjectID: &object, RecordIDs: []string{"ISS-1"}},
	)
	if err != nil || len(response.Results) != 1 || server.identityCalls.Load() != 0 || server.recordsCalls.Load() != 1 ||
		client.Metrics() != (RuntimeClientMetrics{RecordsCalls: 1}) {
		t.Fatalf("response=%#v err=%v calls=%d/%d metrics=%#v", response, err, server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics())
	}
}

func TestQueryRuntimeOwnsIdentityThenExactlyOneDataRPC(t *testing.T) {
	for _, reject := range []bool{false, true} {
		t.Run(map[bool]string{false: "success", true: "identity rejection"}[reject], func(t *testing.T) {
			target, server, transport, cleanup := startRuntimeClientTestServer(t, reject)
			defer cleanup()
			server.expectedIdentityCapability = querysemantic.CapabilityName
			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
			defer cancel()
			client, err := dialQueryRuntimeWithCredentials(ctx, RuntimeClientConfig{
				Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
			}, transport)
			if err != nil {
				t.Fatal(err)
			}
			defer client.Close()
			response, err := client.Query(ctx, "user-secret", querysemantic.Request{
				ObjectID: "Issue", Selections: []querysemantic.Selection{{Kind: querysemantic.SelectionField, Field: "RetryCount"}}, Limit: 1,
			})
			if reject {
				var failure *identitysemantic.Failure
				if !errors.As(err, &failure) || failure.Category != identitysemantic.FailureUnauthenticated || server.recordsCalls.Load() != 0 {
					t.Fatalf("error=%v records=%d", err, server.recordsCalls.Load())
				}
				return
			}
			if err != nil || len(response.Records) != 1 || response.Records[0].Fields[0].Value.Unsigned == nil || *response.Records[0].Fields[0].Value.Unsigned != 0 ||
				server.identityCalls.Load() != 1 || server.recordsCalls.Load() != 1 || client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 1, RecordsCalls: 1}) {
				t.Fatalf("response=%#v err=%v calls=%d/%d metrics=%#v", response, err, server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics())
			}
		})
	}
}

func TestQueryRuntimeHistoryUsesSameCapabilityAndOneDataRPC(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.expectedIdentityCapability = querysemantic.CapabilityName
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialQueryRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	response, err := client.QueryHistory(ctx, "user-secret", querysemantic.HistoryRequest{ParentObjectID: "Issue", ParentRecordID: "ISS-1", SelectedFields: []string{"Status"}, Limit: 1})
	if err != nil || len(response.Rows) != 1 || response.Rows[0].ParentID != "ISS-1" || server.identityCalls.Load() != 1 || server.recordsCalls.Load() != 1 {
		t.Fatalf("response=%#v err=%v calls=%d/%d", response, err, server.identityCalls.Load(), server.recordsCalls.Load())
	}
}

func TestRelatedCountRuntimeExchangesIdentityThenUsesOneDataRPC(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.expectedIdentityCapability = relatedsemantic.CapabilityName
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRelatedCountRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	response, err := client.Count(ctx, "user-secret", relatedsemantic.Request{Counts: []relatedsemantic.CountSpec{
		{ParentRecordID: "P-1", ChildObjectID: "WorkItem", RelationshipField: "ParentId"},
		{ParentRecordID: "P-1", ChildObjectID: "Comment", RelationshipField: "OwnerId", MatchValue: "owner-key"},
	}})
	if err != nil || len(response.Results) != 2 || response.Results[0].Count != 0 || response.Results[1].Count != 1 || server.identityCalls.Load() != 1 || server.recordsCalls.Load() != 1 || client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 1, RecordsCalls: 1}) {
		t.Fatalf("response=%#v err=%v calls=%d/%d metrics=%#v", response, err, server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics())
	}
}

func TestRecycleBinRuntimeExchangesIdentityThenUsesOneDataRPC(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.expectedIdentityCapability = recyclesemantic.CapabilityName
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRecycleBinRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	response, err := client.List(ctx, "user-secret", recyclesemantic.Request{Scope: recyclesemantic.ScopeMine})
	if err != nil || len(response.Items) != 1 || response.Items[0].RecordID != "row-1" ||
		server.identityCalls.Load() != 1 || server.recordsCalls.Load() != 1 ||
		client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 1, RecordsCalls: 1}) {
		t.Fatalf("response=%#v err=%v calls=%d/%d metrics=%#v", response, err, server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics())
	}
}

func TestRecycleBinRuntimeMutationsShareOneLeastPrivilegeDelegation(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.expectedIdentityCapability = recyclemutationsemantic.CapabilityName
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRecycleBinRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	if _, err := client.Restore(ctx, "user-secret", recyclemutationsemantic.Request{RecordID: "row-1"}); err != nil {
		t.Fatalf("restore: %v", err)
	}
	if _, err := client.Purge(ctx, "user-secret", recyclemutationsemantic.Request{RecordID: "row-2"}); err != nil {
		t.Fatalf("purge: %v", err)
	}
	if server.identityCalls.Load() != 1 || server.recordsCalls.Load() != 2 ||
		client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 1, RecordsCalls: 2}) ||
		!slices.Equal(server.requestedIdentityCapabilities(), []string{recyclemutationsemantic.CapabilityName}) {
		t.Fatalf("calls=%d/%d metrics=%#v exchanged=%#v", server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics(), server.requestedIdentityCapabilities())
	}
}

func TestRecordViewProofRuntimeExchangesLeastPrivilegeIdentityForEachBoundedDataCall(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRecordViewProofRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	read, err := client.Read(ctx, "user-secret", viewsemantic.ReadRequest{
		ObjectID: "WorkItem", Selector: viewsemantic.Selector{Kind: viewsemantic.SelectorRecordID, RecordID: "row-1"}, Projection: viewsemantic.ProjectionRoot,
	})
	if err != nil || read.RecordID != "row-1" {
		t.Fatalf("read=%#v err=%v", read, err)
	}
	resolved, err := client.ResolveAttachmentSourceProofs(ctx, "user-secret", attachmentsemantic.ResolveRequest{
		ParentObjectID: "WorkItem", ParentRecordID: "row-1", ResolverID: "attachment",
		Sources: []attachmentsemantic.Source{{SourceFieldID: "DocumentLink", SourceValue: "source-1", SourceValueOffset: 7}},
	})
	if err != nil || len(resolved.Results) != 1 || resolved.Results[0].Outcome != attachmentsemantic.SourceNotFound ||
		server.identityCalls.Load() != 2 || server.recordsCalls.Load() != 2 ||
		server.legacyAttachmentCalls.Load() != 0 ||
		client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 2, RecordsCalls: 2}) ||
		!slices.Equal(server.requestedIdentityCapabilities(), []string{viewsemantic.CapabilityName, attachmentsemantic.CapabilityName}) {
		t.Fatalf("resolved=%#v err=%v calls=%d/%d metrics=%#v exchanged=%#v", resolved, err, server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics(), server.requestedIdentityCapabilities())
	}
	if server.legacyAttachmentCalls.Load() != 0 {
		t.Fatalf("proof client invoked the historic attachment RPC %d time(s)", server.legacyAttachmentCalls.Load())
	}
}

func TestRecordViewProofRuntimeReusesRequestScopedDelegation(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRecordViewProofRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()

	response, err := client.Read(
		withRequestScopedRuntimeDelegation(
			ctx, "header.payload.signature", client.base.identityCoordinator, client.base.appInstallationID,
		),
		"must-not-be-exchanged",
		viewsemantic.ReadRequest{
			ObjectID: "WorkItem", Selector: viewsemantic.Selector{Kind: viewsemantic.SelectorRecordID, RecordID: "row-1"}, Projection: viewsemantic.ProjectionRoot,
		},
	)
	if err != nil || response.RecordID != "row-1" || server.identityCalls.Load() != 0 || server.recordsCalls.Load() != 1 ||
		client.Metrics() != (RuntimeClientMetrics{RecordsCalls: 1}) {
		t.Fatalf("response=%#v err=%v calls=%d/%d metrics=%#v", response, err, server.identityCalls.Load(), server.recordsCalls.Load(), client.Metrics())
	}
}

func TestLegacyRecordViewRuntimePreservesReleasedAttachmentRPC(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.expectedIdentityCapability = viewsemantic.CapabilityName
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRecordViewRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	resolved, err := client.ResolveAttachmentSources(ctx, "user-secret", viewsemantic.ResolveAttachmentSourcesRequest{
		ParentObjectID: "WorkItem", ParentRecordID: "row-1", ResolverID: "attachment", SourceValues: []string{"source-1"},
	})
	if err != nil || len(resolved.Results) != 1 || resolved.Results[0].Outcome != viewsemantic.AttachmentSourceNotFound ||
		server.identityCalls.Load() != 1 || server.recordsCalls.Load() != 1 || server.legacyAttachmentCalls.Load() != 1 ||
		client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 1, RecordsCalls: 1}) ||
		!slices.Equal(server.requestedIdentityCapabilities(), []string{viewsemantic.CapabilityName}) {
		t.Fatalf("resolved=%#v err=%v calls=%d/%d legacy=%d metrics=%#v exchanged=%#v", resolved, err, server.identityCalls.Load(), server.recordsCalls.Load(), server.legacyAttachmentCalls.Load(), client.Metrics(), server.requestedIdentityCapabilities())
	}
}

func TestRuntimeClientsReconnectIdleConnectionsAfterGracefulRestart(t *testing.T) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	address := listener.Addr().String()
	serverCredentials, clientCredentials := runtimeClientTestCredentials(t)
	implementation := &runtimeClientTestServer{t: t}
	startServer := func(listener net.Listener) *grpc.Server {
		server := grpc.NewServer(grpc.Creds(serverCredentials))
		runtimev1.RegisterIdentityContextServiceServer(server, implementation)
		runtimev1.RegisterRecordsServiceServer(server, implementation)
		runtimev1.RegisterRecordCreateDefaultPreviewServiceServer(server, implementation)
		runtimev1.RegisterRecordQueryServiceServer(server, implementation)
		runtimev1.RegisterRelatedRecordCountServiceServer(server, implementation)
		runtimev1.RegisterRecycleBinReadServiceServer(server, implementation)
		runtimev1.RegisterRecycleBinMutationServiceServer(server, implementation)
		runtimev1.RegisterRecordViewReadServiceServer(server, implementation)
		runtimev1.RegisterRecordAttachmentSourceResolutionServiceServer(server, implementation)
		runtimev1.RegisterRecordMutationsServiceServer(server, implementation)
		go func() { _ = server.Serve(listener) }()
		return server
	}
	server := startServer(listener)

	startupCtx, cancelStartup := context.WithTimeout(context.Background(), 5*time.Second)
	config := RuntimeClientConfig{
		Target: "passthrough:///" + address, AppInstallationID: "installation-1",
		WorkloadToken: "workload-secret", AttemptTimeout: 250 * time.Millisecond,
	}
	accessClient, err := dialRecordAccessRuntimeWithCredentials(startupCtx, config, clientCredentials)
	if err != nil {
		cancelStartup()
		t.Fatal(err)
	}
	defer accessClient.Close()
	queryClient, err := dialQueryRuntimeWithCredentials(startupCtx, config, clientCredentials)
	if err != nil {
		cancelStartup()
		t.Fatal(err)
	}
	defer queryClient.Close()
	recycleBinClient, err := dialRecycleBinRuntimeWithCredentials(startupCtx, config, clientCredentials)
	if err != nil {
		cancelStartup()
		t.Fatal(err)
	}
	defer recycleBinClient.Close()
	recordViewClient, err := dialRecordViewRuntimeWithCredentials(startupCtx, config, clientCredentials)
	if err != nil {
		cancelStartup()
		t.Fatal(err)
	}
	defer recordViewClient.Close()
	mutationClient, err := dialRecordMutationRuntimeWithCredentials(startupCtx, config, clientCredentials)
	cancelStartup()
	if err != nil {
		t.Fatal(err)
	}
	defer mutationClient.Close()

	server.GracefulStop()
	_ = listener.Close()
	waitForRuntimeClientState(t, 5*time.Second, func() bool {
		return !accessClient.Ready() && !queryClient.Ready() && !recycleBinClient.Ready() && !recordViewClient.Ready() && !mutationClient.Ready()
	}, "clients did not observe graceful Runtime stop")

	requestCtx, cancelRequest := context.WithTimeout(context.Background(), time.Second)
	object := "issue"
	_, _ = accessClient.Evaluate(requestCtx, "user-secret", semantic.Request{ObjectID: &object, RecordIDs: []string{"ISS-1"}})
	cancelRequest()

	listener, err = net.Listen("tcp", address)
	if err != nil {
		t.Fatal(err)
	}
	server = startServer(listener)
	defer server.GracefulStop()
	defer listener.Close()
	waitForRuntimeClientState(t, 5*time.Second, func() bool {
		return accessClient.Ready() && queryClient.Ready() && recycleBinClient.Ready() && recordViewClient.Ready() && mutationClient.Ready()
	}, "idle Runtime client did not reconnect after graceful restart")
	restoredCtx, cancelRestored := context.WithTimeout(context.Background(), time.Second)
	defer cancelRestored()
	if _, err := recycleBinClient.Restore(restoredCtx, "user-secret", recyclemutationsemantic.Request{RecordID: "row-after-restart"}); err != nil {
		t.Fatalf("recycle-bin mutation after Runtime restart: %v", err)
	}
}

func waitForRuntimeClientState(t *testing.T, timeout time.Duration, condition func() bool, message string) {
	t.Helper()
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if condition() {
			return
		}
		time.Sleep(10 * time.Millisecond)
	}
	t.Fatal(message)
}

func TestRuntimeProductionDialRequiresVerifiedTLS(t *testing.T) {
	tests := []struct {
		name string
		dial func(context.Context, RuntimeClientConfig) error
	}{
		{"RecordAccess", func(ctx context.Context, config RuntimeClientConfig) error {
			_, err := DialRecordAccessRuntime(ctx, config)
			return err
		}},
		{"Query", func(ctx context.Context, config RuntimeClientConfig) error {
			_, err := DialQueryRuntime(ctx, config)
			return err
		}},
		{"RelatedCount", func(ctx context.Context, config RuntimeClientConfig) error {
			_, err := DialRelatedCountRuntime(ctx, config)
			return err
		}},
		{"RecycleBin", func(ctx context.Context, config RuntimeClientConfig) error {
			_, err := DialRecycleBinRuntime(ctx, config)
			return err
		}},
		{"RecordView", func(ctx context.Context, config RuntimeClientConfig) error {
			_, err := DialRecordViewRuntime(ctx, config)
			return err
		}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()
			err := tt.dial(ctx, RuntimeClientConfig{Target: "passthrough:///runtime:9090", AppInstallationID: "installation-1", WorkloadToken: "secret", AttemptTimeout: time.Second})
			if err == nil || !strings.Contains(err.Error(), "CA file and server name are required") {
				t.Fatalf("error = %v", err)
			}
		})
	}
}

func TestRecordAccessRuntimeRequiresExplicitAttemptTimeout(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	_, err := DialRecordAccessRuntime(ctx, RuntimeClientConfig{
		Target: "passthrough:///runtime:9090", AppInstallationID: "installation-1", WorkloadToken: "secret",
	})
	if err == nil || !strings.Contains(err.Error(), "attempt timeout") {
		t.Fatalf("error = %v", err)
	}
}

func TestRecordAccessRuntimeDerivesOneFreshDeadlinePerRPC(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.identityDelay = 100 * time.Millisecond
	server.maximumRecordLease = 300 * time.Millisecond

	routeCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialRecordAccessRuntimeWithCredentials(routeCtx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1", WorkloadToken: "workload-secret", AttemptTimeout: 200 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	object := "issue"
	if _, err := client.Evaluate(routeCtx, "user-secret", semantic.Request{ObjectID: &object, RecordIDs: []string{"ISS-1"}}); err != nil {
		t.Fatalf("Evaluate() error = %v", err)
	}
	if got := time.Duration(server.recordsDeadlineRemaining.Load()); got <= 0 || got > server.maximumRecordLease {
		t.Fatalf("Records deadline remaining = %v, want one fresh per-RPC attempt deadline", got)
	}
}

func TestCreateDefaultPreviewRuntimeOwnsIdentityThenExactlyOneDataRPC(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.expectedIdentityCapability = defaultsemantic.CapabilityName
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	client, err := dialCreateDefaultPreviewRuntimeWithCredentials(ctx, RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1",
		WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}, transport)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	parentField := "ParentId"
	parentRecord := "parent-1"
	response, err := client.Preview(ctx, "user-secret", defaultsemantic.Request{
		ObjectID: "RankedChild", ParentFieldID: &parentField,
		ParentRecordID: &parentRecord,
	})
	if err != nil || len(response.Fields) != 1 || response.Fields[0].Integer == nil ||
		*response.Fields[0].Integer != 901 || server.identityCalls.Load() != 1 ||
		server.recordsCalls.Load() != 1 ||
		client.Metrics() != (RuntimeClientMetrics{IdentityCalls: 1, RecordsCalls: 1}) ||
		!slices.Equal(server.requestedIdentityCapabilities(), []string{defaultsemantic.CapabilityName}) {
		t.Fatalf("response=%#v err=%v calls=%d/%d metrics=%#v exchanged=%#v",
			response, err, server.identityCalls.Load(), server.recordsCalls.Load(),
			client.Metrics(), server.requestedIdentityCapabilities())
	}
}

func TestCreateDefaultPreviewRuntimeReusesRequestScopedDelegation(t *testing.T) {
	target, server, transport, cleanup := startRuntimeClientTestServer(t, false)
	defer cleanup()
	server.expectedIdentityCapability = defaultsemantic.CapabilityName
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	config := RuntimeClientConfig{
		Target: target, AppInstallationID: "installation-1",
		WorkloadToken: "workload-secret", AttemptTimeout: 500 * time.Millisecond,
	}
	coordinator := newIdentityExchangeCoordinator(config)
	platform, err := dialPlatformRuntimeWithCredentialsAndCoordinator(ctx, config, transport, coordinator)
	if err != nil {
		t.Fatal(err)
	}
	defer platform.Close()
	defaults, err := dialCreateDefaultPreviewRuntimeWithCredentialsAndCoordinator(ctx, config, transport, coordinator)
	if err != nil {
		t.Fatal(err)
	}
	defer defaults.Close()

	authorizedCtx, err := platform.AuthorizeCapabilities(ctx, "user-secret", []string{defaultsemantic.CapabilityName})
	if err != nil {
		t.Fatal(err)
	}
	parentField := "ParentId"
	parentRecord := "parent-1"
	response, err := defaults.Preview(authorizedCtx, "must-not-be-exchanged", defaultsemantic.Request{
		ObjectID: "RankedChild", ParentFieldID: &parentField,
		ParentRecordID: &parentRecord,
	})
	if err != nil || len(response.Fields) != 1 || response.Fields[0].Integer == nil ||
		*response.Fields[0].Integer != 901 || server.identityCalls.Load() != 1 ||
		server.recordsCalls.Load() != 1 ||
		defaults.Metrics() != (RuntimeClientMetrics{RecordsCalls: 1}) {
		t.Fatalf("response=%#v err=%v calls=%d/%d defaults=%#v",
			response, err, server.identityCalls.Load(), server.recordsCalls.Load(),
			defaults.Metrics())
	}
}

func startRuntimeClientTestServer(t *testing.T, reject bool) (string, *runtimeClientTestServer, credentials.TransportCredentials, func()) {
	t.Helper()
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	serverCredentials, clientCredentials := runtimeClientTestCredentials(t)
	server := grpc.NewServer(grpc.Creds(serverCredentials))
	implementation := &runtimeClientTestServer{rejectIdentity: reject, t: t}
	runtimev1.RegisterIdentityContextServiceServer(server, implementation)
	runtimev1.RegisterRecordsServiceServer(server, implementation)
	runtimev1.RegisterRecordCreateDefaultPreviewServiceServer(server, implementation)
	runtimev1.RegisterRecordQueryServiceServer(server, implementation)
	runtimev1.RegisterRelatedRecordCountServiceServer(server, implementation)
	runtimev1.RegisterRecycleBinReadServiceServer(server, implementation)
	runtimev1.RegisterRecycleBinMutationServiceServer(server, implementation)
	runtimev1.RegisterRecordViewReadServiceServer(server, implementation)
	runtimev1.RegisterRecordAttachmentSourceResolutionServiceServer(server, implementation)
	go func() { _ = server.Serve(listener) }()
	return "passthrough:///" + listener.Addr().String(), implementation, clientCredentials, func() {
		server.GracefulStop()
		_ = listener.Close()
	}
}

func runtimeClientTestCredentials(t *testing.T) (credentials.TransportCredentials, credentials.TransportCredentials) {
	t.Helper()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	now := time.Now()
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "runtime.test"},
		NotBefore: now.Add(-time.Minute), NotAfter: now.Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, DNSNames: []string{"runtime.test"},
	}
	der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		t.Fatal(err)
	}
	keyDER, err := x509.MarshalPKCS8PrivateKey(key)
	if err != nil {
		t.Fatal(err)
	}
	certificatePEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
	certificate, err := tls.X509KeyPair(certificatePEM, keyPEM)
	if err != nil {
		t.Fatal(err)
	}
	roots := x509.NewCertPool()
	if !roots.AppendCertsFromPEM(certificatePEM) {
		t.Fatal("append certificate")
	}
	return credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{certificate}}),
		credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS13, RootCAs: roots, ServerName: "runtime.test"})
}
