Ctrl K
ComunidadGuías…

aggregator_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package aastro

import (
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

func TestAggregator_MergeStrategy(t *testing.T) {
	agg := &defaultAggregator{}
	responses := []upstreamResponse{
		okResponse(`{"a":1,"b":2}`),
		okResponse(`{"b":99,"c":4}`),
	}

	t.Run("conflict policies", func(t *testing.T) {
		tests := []struct {
			name     string
			policy   conflictPolicy
			expected string
		}{
			{"overwrite uses last value", conflictPolicyOverwrite, `{"a":1,"b":99,"c":4}`},
			{"first preserves earliest value", conflictPolicyFirst, `{"a":1,"b":2,"c":4}`},
		}

		for _, tt := range tests {
			t.Run(tt.name, func(t *testing.T) {
				result := agg.aggregate(nil, responses, aggregation{
					strategy:       strategyMerge,
					conflictPolicy: tt.policy,
				}, zap.NewNop())

				assert.Empty(t, result.errors)
				jsonEqual(t, tt.expected, result.data)
			})
		}
	})

	t.Run("returns conflict error when policy is error", func(t *testing.T) {
		result := agg.aggregate(nil, responses, aggregation{
			strategy:       strategyMerge,
			conflictPolicy: conflictPolicyError,
		}, zap.NewNop())

		assert.Nil(t, result.data)
		assert.ElementsMatch(t, []ClientError{ClientErrValueConflict}, result.errors)
	})

	t.Run("returns partial result when best effort and one upstream fails", func(t *testing.T) {
		result := agg.aggregate(
			nil,
			[]upstreamResponse{
				okResponse(`{"a":1}`),
				errResponse(upstreamTimeout),
			},
			aggregation{
				strategy:       strategyMerge,
				bestEffort:     true,
				conflictPolicy: conflictPolicyOverwrite,
			},
			zap.NewNop(),
		)

		assert.True(t, result.partial)
		assert.ElementsMatch(t, []ClientError{ClientErrUpstreamUnavailable}, result.errors)
		jsonEqual(t, `{"a":1}`, result.data)
	})
}

func TestAggregator_ArrayStrategy(t *testing.T) {
	agg := &defaultAggregator{}

	t.Run("aggregates responses into an array", func(t *testing.T) {
		result := agg.aggregate(
			nil,
			[]upstreamResponse{
				okResponse(`{"x":1}`),
				okResponse(`{"y":2}`),
			},
			aggregation{strategy: strategyArray},
			zap.NewNop(),
		)

		assert.Empty(t, result.errors)
		assert.False(t, result.partial)
		jsonEqual(t, `[{"x":1},{"y":2}]`, result.data)
	})

	t.Run("skips failed upstream when best effort", func(t *testing.T) {
		result := agg.aggregate(
			nil,
			[]upstreamResponse{
				okResponse(`{"x":1}`),
				errResponse(upstreamBadStatus),
			},
			aggregation{strategy: strategyArray, bestEffort: true},
			zap.NewNop(),
		)

		assert.True(t, result.partial)
		assert.ElementsMatch(t, []ClientError{ClientErrUpstreamError}, result.errors)
		jsonEqual(t, `[{"x":1}]`, result.data)
	})
}

func TestAggregator_NamespaceStrategy(t *testing.T) {
	agg := &defaultAggregator{}

	t.Run("groups responses by upstream name", func(t *testing.T) {
		result := agg.aggregate(
			mockUpstreams("users", "orders"),
			[]upstreamResponse{
				okResponse(`{"id":1}`),
				okResponse(`{"total":99}`),
			},
			aggregation{strategy: strategyNamespace},
			zap.NewNop(),
		)

		assert.Empty(t, result.errors)
		jsonEqual(t, `{"users":{"id":1},"orders":{"total":99}}`, result.data)
	})

	t.Run("omits failed upstream when best effort", func(t *testing.T) {
		result := agg.aggregate(
			mockUpstreams("users", "orders"),
			[]upstreamResponse{
				okResponse(`{"id":1}`),
				errResponse(upstreamTimeout),
			},
			aggregation{strategy: strategyNamespace, bestEffort: true},
			zap.NewNop(),
		)

		assert.True(t, result.partial)
		assert.Len(t, result.errors, 1)

		var got map[string]any
		require.NoError(t, decodeJSONInto(result.data, &got))
		assert.Contains(t, got, "users")
		assert.NotContains(t, got, "orders")
	})
}

func TestMapUpstreamError(t *testing.T) {
	tests := []struct {
		name string
		kind upstreamErrorKind
		want ClientError
	}{
		{"timeout → upstream unavailable", upstreamTimeout, ClientErrUpstreamUnavailable},
		{"connection → upstream unavailable", upstreamConnection, ClientErrUpstreamUnavailable},
		{"bad status → upstream error", upstreamBadStatus, ClientErrUpstreamError},
		{"body too large → upstream body too large", upstreamBodyTooLarge, ClientErrUpstreamBodyTooLarge},
		{"internal → internal", upstreamInternal, ClientErrInternal},
		{"read error → internal", upstreamReadError, ClientErrInternal},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := mapUpstreamError(&upstreamError{
				kind: tt.kind,
				err:  errors.New("err"),
			})
			assert.Equal(t, tt.want, got)
		})
	}
}