From c47b0a346e2627f600baabe4d145a8ba0c47aaf4 Mon Sep 17 00:00:00 2001
From: Semyon Krestyaninov <krestyaninov@perx.ru>
Date: Wed, 4 Jun 2025 11:48:11 +0000
Subject: [PATCH] =?UTF-8?q?fix(core):=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?=
 =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=82?=
 =?UTF-8?q?=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D1=8B=D1=85=20=D0=B4?=
 =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D1=80=D0=B8=20=D0=BE?=
 =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BA=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B8=D1=80?=
 =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F/=D0=B4=D0=B5=D0=BA=D0=BE?=
 =?UTF-8?q?=D0=B4=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2?=
 =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=B5=20`Introspect`=20=D0=B2?=
 =?UTF-8?q?=20`ClientEncodeMiddleware`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Issue: #3273
---
 .../middleware/client_encode_middleware.go    |  17 ++-
 .../client_encode_middleware_test.go          | 144 ++++++++++++++++++
 2 files changed, 154 insertions(+), 7 deletions(-)
 create mode 100644 pkg/items/middleware/client_encode_middleware_test.go

diff --git a/pkg/items/middleware/client_encode_middleware.go b/pkg/items/middleware/client_encode_middleware.go
index 324c648e..476687b8 100644
--- a/pkg/items/middleware/client_encode_middleware.go
+++ b/pkg/items/middleware/client_encode_middleware.go
@@ -31,18 +31,21 @@ func (m *encodeDecodeMiddleware) Introspect(ctx context.Context, item *items.Ite
 		return nil, nil, err
 	}
 
-	if item, err = item.Encode(ctx, coll.Schema); err != nil {
-		return
+	encoded, err := item.Encode(ctx, coll.Schema)
+	if err != nil {
+		// Возвращаем исходные данные для возможности обработки полей с ошибками.
+		return item, coll.Schema, err
 	}
 
-	itm, sch, err = m.next.Introspect(ctx, item, opts...)
-	if itm != nil && sch != nil {
+	introspected, sch, err := m.next.Introspect(ctx, encoded, opts...)
+	if introspected != nil && sch != nil {
 		var err error
-		if itm, err = itm.Decode(ctx, sch); err != nil {
-			return nil, nil, err
+		if introspected, err = introspected.Decode(ctx, sch); err != nil {
+			// Возвращаем исходные данные для возможности обработки полей с ошибками.
+			return item, coll.Schema, err
 		}
 	}
-	return itm, sch, err
+	return introspected, sch, err
 
 }
 
diff --git a/pkg/items/middleware/client_encode_middleware_test.go b/pkg/items/middleware/client_encode_middleware_test.go
new file mode 100644
index 00000000..4adbdb65
--- /dev/null
+++ b/pkg/items/middleware/client_encode_middleware_test.go
@@ -0,0 +1,144 @@
+package middleware
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	collectionsmocks "git.perx.ru/perxis/perxis-go/pkg/collections/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	itemsmocks "git.perx.ru/perxis/perxis-go/pkg/items/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/modify"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+func TestEncodeDecodeMiddleware_Introspect(t *testing.T) {
+	t.Run("Successful", func(t *testing.T) {
+		sch := schema.New(
+			"date", field.Time(),
+		)
+
+		itemsMock := itemsmocks.NewItems(t)
+		itemsMock.On("Introspect", mock.Anything, mock.Anything).Return(&items.Item{
+			ID:           "",
+			SpaceID:      "space",
+			EnvID:        "env",
+			CollectionID: "coll",
+			Data: map[string]any{
+				"date": time.Date(2025, 12, 1, 0, 0, 0, 0, time.UTC),
+			},
+		}, sch, nil).Once()
+
+		collectionsMock := collectionsmocks.NewCollections(t)
+		collectionsMock.On("Get", mock.Anything, "space", "env", "coll").Return(&collections.Collection{
+			ID:      "coll",
+			SpaceID: "space",
+			EnvID:   "env",
+			Schema:  sch,
+		}, nil).Once()
+
+		middleware := ClientEncodeMiddleware(collectionsMock)(itemsMock)
+		item, sch, err := middleware.Introspect(context.Background(), &items.Item{
+			ID:           "id",
+			SpaceID:      "space",
+			EnvID:        "env",
+			CollectionID: "coll",
+		})
+		require.NoError(t, err)
+		assert.Equal(t, map[string]any{"date": time.Date(2025, 12, 1, 0, 0, 0, 0, time.UTC)}, item.Data)
+		assert.NotNil(t, sch)
+	})
+	t.Run("Error during encoding", func(t *testing.T) {
+		sch := schema.New(
+			"date", field.Time(),
+		)
+
+		itemsMock := itemsmocks.NewItems(t)
+		collectionsMock := collectionsmocks.NewCollections(t)
+		collectionsMock.On("Get", mock.Anything, "space", "env", "coll").Return(&collections.Collection{
+			ID:      "coll",
+			SpaceID: "space",
+			EnvID:   "env",
+			Schema:  sch,
+		}, nil).Once()
+
+		middleware := ClientEncodeMiddleware(collectionsMock)(itemsMock)
+		item, sch, err := middleware.Introspect(context.Background(), &items.Item{
+			ID:           "",
+			SpaceID:      "space",
+			EnvID:        "env",
+			CollectionID: "coll",
+			Data: map[string]any{
+				"date": "invalid date",
+			},
+		})
+		require.EqualError(
+			t,
+			err,
+			"encode error: 1 error occurred:\n\t* field 'date': TimeType: encode: unsupported value type : \"string\"\n\n",
+		)
+		assert.Equal(t,
+			map[string]any{
+				"date": "invalid date",
+			},
+			item.Data,
+			"При возникновении ошибки декодирования в ответе должны быть возвращены исходные данные",
+		)
+		assert.NotNil(t, sch)
+	})
+	t.Run("Error during decoding", func(t *testing.T) {
+		sch := schema.New(
+			"num", field.Number(field.NumberFormatInt),
+			"str", field.String().AddOptions(modify.Value("num")),
+		)
+
+		itemsMock := itemsmocks.NewItems(t)
+		itemsMock.On("Introspect", mock.Anything, mock.Anything).Return(&items.Item{
+			ID:           "id",
+			SpaceID:      "space",
+			EnvID:        "env",
+			CollectionID: "coll",
+			Data: map[string]any{
+				"num": 100,
+				"str": 100,
+			},
+		}, sch, nil).Once()
+
+		collectionsMock := collectionsmocks.NewCollections(t)
+		collectionsMock.On("Get", mock.Anything, "space", "env", "coll").Return(&collections.Collection{
+			ID:      "coll",
+			SpaceID: "space",
+			EnvID:   "env",
+			Schema:  sch,
+		}, nil).Once()
+
+		middleware := ClientEncodeMiddleware(collectionsMock)(itemsMock)
+		item, sch, err := middleware.Introspect(context.Background(), &items.Item{
+			ID:           "id",
+			SpaceID:      "space",
+			EnvID:        "env",
+			CollectionID: "coll",
+			Data: map[string]any{
+				"num": 100,
+			},
+		})
+		require.EqualError(
+			t,
+			err,
+			"decode error: 1 error occurred:\n\t* field 'str': StringField decode error: unsupported value type : \"int\"\n\n",
+		)
+		assert.Equal(t,
+			map[string]any{
+				"num": 100,
+			},
+			item.Data,
+			"При возникновении ошибки кодирования в ответе должны быть возвращены исходные данные",
+		)
+		assert.NotNil(t, sch)
+	})
+}
-- 
GitLab