|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// |
| 3 | +// The OpenSearch Contributors require contributions made to |
| 4 | +// this file be licensed under the Apache-2.0 license or a |
| 5 | +// compatible open source license. |
| 6 | + |
| 7 | +package main |
| 8 | + |
| 9 | +import ( |
| 10 | + "strings" |
| 11 | + "testing" |
| 12 | + |
| 13 | + "github.com/stretchr/testify/require" |
| 14 | +) |
| 15 | + |
| 16 | +// hop_v3_to_v4_test.go pins the concrete v3->v4 facts the rewriter depends on. |
| 17 | +// The v3->v4 hop carries no type/field/method tables (the surface diff plus the |
| 18 | +// fail-loud default cover it), so these assertions pin the diff-derived behavior |
| 19 | +// instead: the pointer-wraps the diff produces, the error-model followup, the |
| 20 | +// deliberate fail-loud handling of the redesigned response fields, and the fact |
| 21 | +// that the hop chains cleanly onto v4->v5. |
| 22 | + |
| 23 | +// planV3toV4 returns the single hop plan for v3->v4. |
| 24 | +func planV3toV4(t *testing.T) hopPlan { |
| 25 | + t.Helper() |
| 26 | + plans, err := planChain(3, 4) |
| 27 | + require.NoError(t, err) |
| 28 | + require.Len(t, plans, 1, "v3->v4 is a single hop") |
| 29 | + return plans[0] |
| 30 | +} |
| 31 | + |
| 32 | +// TestHopV3toV4_KnownChanges verifies the v3->v4 delta carries the pointer-wraps |
| 33 | +// the diff derives and the error-model followup, and that no type/field tables |
| 34 | +// were hand-authored for this hop (all changes are diff-derived or fail-loud). |
| 35 | +func TestHopV3toV4_KnownChanges(t *testing.T) { |
| 36 | + p := planV3toV4(t) |
| 37 | + |
| 38 | + // No hand-authored renames or dispositions: the diff plus the fail-loud |
| 39 | + // default carry this hop. |
| 40 | + require.Empty(t, p.renames, "v3->v4 declares no type renames") |
| 41 | + require.Empty(t, p.regroups, "v3->v4 declares no method regroups") |
| 42 | + |
| 43 | + // A field that became a pointer in v4 is auto-detected as a pointerWrap by the |
| 44 | + // surface diff (no table entry needed): CatNodesItemResp.CPU int -> *int. |
| 45 | + assertChangeKind(t, p.delta.Structs[v3api+".CatNodesItemResp"].Changes, "CPU", "pointerWrap") |
| 46 | + |
| 47 | + // The error-model move is reported as a semantic followup, never rewritten. |
| 48 | + require.True(t, containsSubstr(p.followups, "opensearchapi.{Error,Err,RootCause,StringError}"), |
| 49 | + "v3->v4 must report the error-package move as a followup") |
| 50 | + require.True(t, containsSubstr(p.followups, "opensearch.StructError"), |
| 51 | + "v3->v4 must flag the Error -> StructError shape change") |
| 52 | +} |
| 53 | + |
| 54 | +// TestHopV3toV4_FailLoudForRedesignedFields asserts that the response fields |
| 55 | +// redesigned away in v4 (opensearchapi.*Resp.Indices, replaced by an unexported |
| 56 | +// field + GetIndices() accessor) are reported as "unclassified" rather than |
| 57 | +// silently dropped or wrongly renamed. This is deliberate: no disposition is |
| 58 | +// authored up front, so the fail-loud default surfaces the field only if a real |
| 59 | +// consumer actually reads it, at which point a proven ruling is added. |
| 60 | +func TestHopV3toV4_FailLoudForRedesignedFields(t *testing.T) { |
| 61 | + d := planV3toV4(t).delta |
| 62 | + |
| 63 | + // Indices vanished on all six *Resp types; each must be unclassified. |
| 64 | + for _, typ := range []string{ |
| 65 | + "AliasGetResp", "IndicesGetResp", "IndicesRecoveryResp", |
| 66 | + "MappingFieldResp", "MappingGetResp", "SettingsGetResp", |
| 67 | + } { |
| 68 | + assertChangeKind(t, d.Structs[v3api+"."+typ].Changes, "Indices", "unclassified") |
| 69 | + } |
| 70 | + |
| 71 | + // opensearchtransport.Connection dropped its liveness fields; also fail-loud. |
| 72 | + conn := d.Structs[v3transport+".Connection"].Changes |
| 73 | + for _, f := range []string{"DeadSince", "Failures", "IsDead"} { |
| 74 | + assertChangeKind(t, conn, f, "unclassified") |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +// TestHopV3toV4_ChainsToV5 verifies the registered hop composes: a v3->v5 request |
| 79 | +// yields the two adjacent hops in order, applied serially by the driver. |
| 80 | +func TestHopV3toV4_ChainsToV5(t *testing.T) { |
| 81 | + plans, err := planChain(3, 5) |
| 82 | + require.NoError(t, err) |
| 83 | + require.Len(t, plans, 2, "v3->v5 chains v3->v4 then v4->v5") |
| 84 | + require.Equal(t, [2]Major{3, 4}, [2]Major{plans[0].from, plans[0].to}) |
| 85 | + require.Equal(t, [2]Major{4, 5}, [2]Major{plans[1].from, plans[1].to}) |
| 86 | +} |
| 87 | + |
| 88 | +// containsSubstr reports whether any element of s contains sub. |
| 89 | +func containsSubstr(s []string, sub string) bool { |
| 90 | + for _, v := range s { |
| 91 | + if strings.Contains(v, sub) { |
| 92 | + return true |
| 93 | + } |
| 94 | + } |
| 95 | + return false |
| 96 | +} |
0 commit comments