github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/compat/exhaustive_accumulate_test.go (about)

     1  package compat
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/anthropics/anthropic-sdk-go"
     9  )
    10  
    11  // TestAccumulateExhaustive fails when the generated code gains a stream-event or
    12  // delta variant that Accumulate does not handle, so no new variant is dropped
    13  // silently. On failure, handle the named variant in Accumulate, then add it
    14  // to the list below.
    15  func TestAccumulateExhaustive(t *testing.T) {
    16  	events := []string{
    17  		"content_block_delta", "content_block_start", "content_block_stop",
    18  		"message_delta", "message_start", "message_stop",
    19  	}
    20  	deltas := []string{
    21  		"citations_delta", "input_json_delta", "signature_delta",
    22  		"text_delta", "thinking_delta",
    23  	}
    24  	for _, tt := range []struct {
    25  		name    string
    26  		union   any
    27  		handled []string
    28  	}{
    29  		{"events", anthropic.MessageStreamEventUnion{}, events},
    30  		{"deltas", anthropic.RawContentBlockDeltaUnion{}, deltas},
    31  		{"beta events", anthropic.BetaRawMessageStreamEventUnion{}, events},
    32  		{"beta deltas", anthropic.BetaRawContentBlockDeltaUnion{}, append([]string{"compaction_delta"}, deltas...)},
    33  	} {
    34  		t.Run(tt.name, func(t *testing.T) {
    35  			generated := variants(t, tt.union)
    36  			if extra := diff(generated, tt.handled); len(extra) > 0 {
    37  				t.Errorf("new %s variants %q: handle them in Accumulate, then list them here", tt.name, extra)
    38  			}
    39  			if stale := diff(tt.handled, generated); len(stale) > 0 {
    40  				t.Errorf("listed %s variants no longer generated %q: remove them", tt.name, stale)
    41  			}
    42  		})
    43  	}
    44  }
    45  
    46  // variants lists the wire discriminant of every variant of a union.
    47  func variants(t *testing.T, union any) []string {
    48  	t.Helper()
    49  	var out []string
    50  	for _, v := range unionVariants(reflect.TypeOf(union)) {
    51  		out = append(out, wireType(t, v))
    52  	}
    53  	return out
    54  }
    55  
    56  // diff returns the members of a that b lacks, sorted.
    57  func diff(a, b []string) []string {
    58  	lacks := map[string]bool{}
    59  	for _, s := range b {
    60  		lacks[s] = true
    61  	}
    62  	var out []string
    63  	for _, s := range a {
    64  		if !lacks[s] {
    65  			out = append(out, s)
    66  		}
    67  	}
    68  	sort.Strings(out)
    69  	return out
    70  }
    71  
    72  // TestAccumulateDeltaFieldsExhaustive fails when a message_delta type gains a
    73  // field that Accumulate does not merge into the message. Every value the final
    74  // message_delta carries has to reach the accumulated Message: an ignored one
    75  // leaves the streamed Message differing from the non-streaming one. On failure,
    76  // merge the named field in Accumulate, then add it to the list below.
    77  func TestAccumulateDeltaFieldsExhaustive(t *testing.T) {
    78  	usage := []string{
    79  		"CacheCreationInputTokens", "CacheReadInputTokens", "InputTokens",
    80  		"OutputTokens", "OutputTokensDetails", "ServerToolUse",
    81  	}
    82  	delta := []string{"Container", "StopDetails", "StopReason", "StopSequence"}
    83  	event := []string{"Delta", "Type", "Usage"}
    84  	for _, tt := range []struct {
    85  		name    string
    86  		value   any
    87  		handled []string
    88  	}{
    89  		{"delta usage", anthropic.MessageDeltaUsage{}, usage},
    90  		{"beta delta usage", anthropic.BetaMessageDeltaUsage{}, append([]string{"FallbackCredit", "Iterations"}, usage...)},
    91  		{"delta", anthropic.MessageDeltaEventDelta{}, delta},
    92  		{"beta delta", anthropic.BetaRawMessageDeltaEventDelta{}, delta},
    93  		{"delta event", anthropic.MessageDeltaEvent{}, event},
    94  		{"beta delta event", anthropic.BetaRawMessageDeltaEvent{}, append([]string{"ContextManagement", "InputTransformations"}, event...)},
    95  	} {
    96  		t.Run(tt.name, func(t *testing.T) {
    97  			generated := fields(tt.value)
    98  			if extra := diff(generated, tt.handled); len(extra) > 0 {
    99  				t.Errorf("new %s fields %q: merge them in Accumulate, then list them here", tt.name, extra)
   100  			}
   101  			if stale := diff(tt.handled, generated); len(stale) > 0 {
   102  				t.Errorf("listed %s fields no longer generated %q: remove them", tt.name, stale)
   103  			}
   104  		})
   105  	}
   106  }
   107  
   108  // fields lists the exported fields of a struct, minus the JSON metadata one.
   109  func fields(value any) []string {
   110  	typ := reflect.TypeOf(value)
   111  	var out []string
   112  	for i := range typ.NumField() {
   113  		field := typ.Field(i)
   114  		if !field.IsExported() || field.Name == "JSON" {
   115  			continue
   116  		}
   117  		out = append(out, field.Name)
   118  	}
   119  	return out
   120  }