From d08e0578b4c14872b7daf033bfa5af5b87c1434a Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:05:18 -0300 Subject: [PATCH 01/24] chore(conductor): Add new track 'Add Individual Key Retrieval Endpoint for tokenization module (by name)' --- conductor/tracks.md | 3 ++ .../index.md | 5 +++ .../metadata.json | 8 +++++ .../plan.md | 31 ++++++++++++++++ .../spec.md | 35 +++++++++++++++++++ 5 files changed, 82 insertions(+) create mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/index.md create mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/metadata.json create mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/plan.md create mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/spec.md diff --git a/conductor/tracks.md b/conductor/tracks.md index bae9481..ad77f3f 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -4,3 +4,6 @@ This file tracks all major tracks for the project. Each track has its own detail --- +- [ ] **Track: Add Individual Key Retrieval Endpoint for tokenization module (by name)** +*Link: [./tracks/tokenization_key_retrieval_20260307/](./tracks/tokenization_key_retrieval_20260307/)* + diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/index.md b/conductor/tracks/tokenization_key_retrieval_20260307/index.md new file mode 100644 index 0000000..2b40a28 --- /dev/null +++ b/conductor/tracks/tokenization_key_retrieval_20260307/index.md @@ -0,0 +1,5 @@ +# Track tokenization_key_retrieval_20260307 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/metadata.json b/conductor/tracks/tokenization_key_retrieval_20260307/metadata.json new file mode 100644 index 0000000..911e87d --- /dev/null +++ b/conductor/tracks/tokenization_key_retrieval_20260307/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "tokenization_key_retrieval_20260307", + "type": "feature", + "status": "new", + "created_at": "2026-03-07T14:45:00Z", + "updated_at": "2026-03-07T14:45:00Z", + "description": "Add Individual Key Retrieval Endpoint for tokenization module (by name)" +} diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md new file mode 100644 index 0000000..d84718b --- /dev/null +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -0,0 +1,31 @@ +# Implementation Plan: Tokenization Key Retrieval by Name + +This plan outlines the steps to add a new endpoint `GET /v1/tokenization/keys/:name` to retrieve a single tokenization key by its name. + +## Phase 1: Domain and Use Case Layer +Add the `GetByName` functionality to the use case layer. + +- [ ] Task: Add `GetByName` to `TokenizationKeyUseCase` interface. + - File: `internal/tokenization/usecase/interface.go` +- [ ] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. + - File: `internal/tokenization/usecase/tokenization_key_usecase.go` + - TDD: Write failing unit tests in `internal/tokenization/usecase/tokenization_key_usecase_test.go` first. +- [ ] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) + +## Phase 2: HTTP Layer +Expose the new functionality through a REST endpoint. + +- [ ] Task: Add `GetByNameHandler` to `TokenizationKeyHandler`. + - File: `internal/tokenization/http/tokenization_key_handler.go` + - TDD: Write failing unit tests in `internal/tokenization/http/tokenization_key_handler_test.go` first. +- [ ] Task: Register the new route `GET /v1/tokenization/keys/:name`. + - File: `internal/app/di_tokenization.go` +- [ ] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) + +## Phase 3: Integration and Documentation +Ensure end-to-end functionality and update documentation. + +- [ ] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. +- [ ] Task: Update project documentation `docs/engines/tokenization.md`. +- [ ] Task: Update OpenAPI specification `docs/openapi.yaml`. +- [ ] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/spec.md b/conductor/tracks/tokenization_key_retrieval_20260307/spec.md new file mode 100644 index 0000000..dd88cae --- /dev/null +++ b/conductor/tracks/tokenization_key_retrieval_20260307/spec.md @@ -0,0 +1,35 @@ +# Specification: Tokenization Key Retrieval by Name + +## Overview +Currently, the tokenization module supports listing keys and rotating keys, but it lacks a direct endpoint to retrieve a single key's metadata by its name. This track adds a new `GET` endpoint to the tokenization key API to allow efficient lookup of individual keys. + +## Functional Requirements +- **Endpoint:** `GET /v1/tokenization/keys/:name` +- **Capability:** `tokenization:read` +- **Input:** `name` (string) as a path parameter. +- **Output:** Returns the latest version of the tokenization key metadata. +- **Status Codes:** + - `200 OK`: Key found. Returns key metadata (ID, Name, Version, FormatType, IsDeterministic, CreatedAt). + - `404 Not Found`: Key with the given name does not exist or has been soft-deleted. + - `401 Unauthorized`: Missing or invalid authentication token. + - `403 Forbidden`: Authenticated client lacks `tokenization:read` capability. + +## Non-Functional Requirements +- **Consistency:** The response format must match the existing `TokenizationKeyResponse` DTO. +- **Performance:** Direct lookup by name should be efficient (indexed in the database). + +## Acceptance Criteria +- [ ] A new method `GetByName` is added to `TokenizationKeyUseCase`. +- [ ] A new handler `GetByNameHandler` is added to `TokenizationKeyHandler`. +- [ ] The route `GET /v1/tokenization/keys/:name` is registered in the application. +- [ ] The endpoint requires the `tokenization:read` capability. +- [ ] Unit tests for the use case and handler are implemented. +- [ ] Integration tests verify the end-to-end functionality (MySQL and PostgreSQL). +- [ ] Updated integration tests in `test/integration/tokenization_flow_test.go`. +- [ ] Updated documentation: `docs/engines/tokenization.md` +- [ ] Updated OpenAPI documentation: `docs/openapi.yaml` + +## Out of Scope +- Retrieving specific versions of a key (only the latest version is returned). +- Retrieving soft-deleted keys. +- Modifying or deleting keys via this endpoint. From 2fe4a7a0db4de7e6eb405dd2b6cd7b5694cbdb99 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:07:24 -0300 Subject: [PATCH 02/24] feat(tokenization): Add GetByName to TokenizationKeyUseCase interface --- internal/tokenization/usecase/interface.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/tokenization/usecase/interface.go b/internal/tokenization/usecase/interface.go index 8b29f0c..01682ca 100644 --- a/internal/tokenization/usecase/interface.go +++ b/internal/tokenization/usecase/interface.go @@ -91,6 +91,10 @@ type TokenizationKeyUseCase interface { // Delete soft deletes a tokenization key and all its versions by key ID. Delete(ctx context.Context, keyID uuid.UUID) error + // GetByName retrieves a single tokenization key by its name. + // Returns the latest version for the key. Filters out soft-deleted keys. + GetByName(ctx context.Context, name string) (*tokenizationDomain.TokenizationKey, error) + // ListCursor retrieves tokenization keys ordered by name ascending with cursor-based pagination. // If afterName is provided, returns keys with name greater than afterName (ASC order). // Returns the latest version for each key. Filters out soft-deleted keys. From cc268cc0dd4404deeca8de3da721840edb046cfb Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:07:40 -0300 Subject: [PATCH 03/24] conductor(plan): Mark task 'Add GetByName to TokenizationKeyUseCase interface' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index d84718b..8bd625a 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -5,9 +5,8 @@ This plan outlines the steps to add a new endpoint `GET /v1/tokenization/keys/:n ## Phase 1: Domain and Use Case Layer Add the `GetByName` functionality to the use case layer. -- [ ] Task: Add `GetByName` to `TokenizationKeyUseCase` interface. - - File: `internal/tokenization/usecase/interface.go` -- [ ] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. +- [x] Task: Add `GetByName` to `TokenizationKeyUseCase` interface. 2fe4a7a +- [~] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. - File: `internal/tokenization/usecase/tokenization_key_usecase.go` - TDD: Write failing unit tests in `internal/tokenization/usecase/tokenization_key_usecase_test.go` first. - [ ] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) From 3ae4bf7f22c34ef2760613349a070cfee66cae5e Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:09:50 -0300 Subject: [PATCH 04/24] feat(tokenization): Implement GetByName in TokenizationKeyUseCase --- internal/tokenization/usecase/mocks/mocks.go | 68 ++++++++ .../tokenization_key_metrics_decorator.go | 19 ++ ...tokenization_key_metrics_decorator_test.go | 80 +++++++++ .../usecase/tokenization_key_usecase.go | 16 ++ .../usecase/tokenization_key_usecase_test.go | 120 +++++++++++++ internal/transit/usecase/mocks/mocks.go | 165 +++++++++--------- 6 files changed, 384 insertions(+), 84 deletions(-) diff --git a/internal/tokenization/usecase/mocks/mocks.go b/internal/tokenization/usecase/mocks/mocks.go index 5452ccf..2ae320a 100644 --- a/internal/tokenization/usecase/mocks/mocks.go +++ b/internal/tokenization/usecase/mocks/mocks.go @@ -1332,6 +1332,74 @@ func (_c *MockTokenizationKeyUseCase_Delete_Call) RunAndReturn(run func(ctx cont return _c } +// GetByName provides a mock function for the type MockTokenizationKeyUseCase +func (_mock *MockTokenizationKeyUseCase) GetByName(ctx context.Context, name string) (*domain0.TokenizationKey, error) { + ret := _mock.Called(ctx, name) + + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + + var r0 *domain0.TokenizationKey + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*domain0.TokenizationKey, error)); ok { + return returnFunc(ctx, name) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) *domain0.TokenizationKey); ok { + r0 = returnFunc(ctx, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain0.TokenizationKey) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, name) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockTokenizationKeyUseCase_GetByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByName' +type MockTokenizationKeyUseCase_GetByName_Call struct { + *mock.Call +} + +// GetByName is a helper method to define mock.On call +// - ctx context.Context +// - name string +func (_e *MockTokenizationKeyUseCase_Expecter) GetByName(ctx interface{}, name interface{}) *MockTokenizationKeyUseCase_GetByName_Call { + return &MockTokenizationKeyUseCase_GetByName_Call{Call: _e.mock.On("GetByName", ctx, name)} +} + +func (_c *MockTokenizationKeyUseCase_GetByName_Call) Run(run func(ctx context.Context, name string)) *MockTokenizationKeyUseCase_GetByName_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockTokenizationKeyUseCase_GetByName_Call) Return(tokenizationKey *domain0.TokenizationKey, err error) *MockTokenizationKeyUseCase_GetByName_Call { + _c.Call.Return(tokenizationKey, err) + return _c +} + +func (_c *MockTokenizationKeyUseCase_GetByName_Call) RunAndReturn(run func(ctx context.Context, name string) (*domain0.TokenizationKey, error)) *MockTokenizationKeyUseCase_GetByName_Call { + _c.Call.Return(run) + return _c +} + // ListCursor provides a mock function for the type MockTokenizationKeyUseCase func (_mock *MockTokenizationKeyUseCase) ListCursor(ctx context.Context, afterName *string, limit int) ([]*domain0.TokenizationKey, error) { ret := _mock.Called(ctx, afterName, limit) diff --git a/internal/tokenization/usecase/tokenization_key_metrics_decorator.go b/internal/tokenization/usecase/tokenization_key_metrics_decorator.go index dd7045a..7f310d2 100644 --- a/internal/tokenization/usecase/tokenization_key_metrics_decorator.go +++ b/internal/tokenization/usecase/tokenization_key_metrics_decorator.go @@ -88,6 +88,25 @@ func (t *tokenizationKeyUseCaseWithMetrics) Delete(ctx context.Context, tokeniza return err } +// GetByName records metrics for tokenization key retrieval operations. +func (t *tokenizationKeyUseCaseWithMetrics) GetByName( + ctx context.Context, + name string, +) (*tokenizationDomain.TokenizationKey, error) { + start := time.Now() + key, err := t.next.GetByName(ctx, name) + + status := "success" + if err != nil { + status = "error" + } + + t.metrics.RecordOperation(ctx, "tokenization", "tokenization_key_get", status) + t.metrics.RecordDuration(ctx, "tokenization", "tokenization_key_get", time.Since(start), status) + + return key, err +} + // List records metrics for tokenization key listing operations. func (t *tokenizationKeyUseCaseWithMetrics) ListCursor( ctx context.Context, diff --git a/internal/tokenization/usecase/tokenization_key_metrics_decorator_test.go b/internal/tokenization/usecase/tokenization_key_metrics_decorator_test.go index 052d683..0c0fef9 100644 --- a/internal/tokenization/usecase/tokenization_key_metrics_decorator_test.go +++ b/internal/tokenization/usecase/tokenization_key_metrics_decorator_test.go @@ -260,6 +260,86 @@ func TestTokenizationKeyUseCaseWithMetrics_Rotate(t *testing.T) { } } +func TestTokenizationKeyUseCaseWithMetrics_GetByName(t *testing.T) { + tests := []struct { + name string + setupMocks func(*tokenizationMocks.MockTokenizationKeyUseCase, *mockBusinessMetrics) + keyName string + expectedKey *tokenizationDomain.TokenizationKey + expectedErr error + expectedStatus string + }{ + { + name: "Success_RecordsSuccessMetrics", + setupMocks: func(mockUseCase *tokenizationMocks.MockTokenizationKeyUseCase, mockMetrics *mockBusinessMetrics) { + key := &tokenizationDomain.TokenizationKey{ + ID: uuid.New(), + Name: "test-key", + Version: 1, + FormatType: tokenizationDomain.FormatUUID, + IsDeterministic: false, + CreatedAt: time.Now().UTC(), + } + mockUseCase.EXPECT(). + GetByName(mock.Anything, "test-key"). + Return(key, nil). + Once() + mockMetrics.On("RecordOperation", mock.Anything, "tokenization", "tokenization_key_get", "success"). + Once() + mockMetrics.On("RecordDuration", mock.Anything, "tokenization", "tokenization_key_get", mock.AnythingOfType("time.Duration"), "success"). + Once() + }, + keyName: "test-key", + expectedKey: &tokenizationDomain.TokenizationKey{ + Name: "test-key", + }, + expectedErr: nil, + expectedStatus: "success", + }, + { + name: "Error_RecordsErrorMetrics", + setupMocks: func(mockUseCase *tokenizationMocks.MockTokenizationKeyUseCase, mockMetrics *mockBusinessMetrics) { + mockUseCase.EXPECT(). + GetByName(mock.Anything, "test-key"). + Return(nil, errors.New("key not found")). + Once() + mockMetrics.On("RecordOperation", mock.Anything, "tokenization", "tokenization_key_get", "error"). + Once() + mockMetrics.On("RecordDuration", mock.Anything, "tokenization", "tokenization_key_get", mock.AnythingOfType("time.Duration"), "error"). + Once() + }, + keyName: "test-key", + expectedKey: nil, + expectedErr: errors.New("key not found"), + expectedStatus: "error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockUseCase := tokenizationMocks.NewMockTokenizationKeyUseCase(t) + mockMetrics := &mockBusinessMetrics{} + tt.setupMocks(mockUseCase, mockMetrics) + + decorator := NewTokenizationKeyUseCaseWithMetrics(mockUseCase, mockMetrics) + + key, err := decorator.GetByName(context.Background(), tt.keyName) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Nil(t, key) + } else { + assert.NoError(t, err) + assert.NotNil(t, key) + assert.Equal(t, tt.expectedKey.Name, key.Name) + } + + mockMetrics.AssertExpectations(t) + mockUseCase.AssertExpectations(t) + }) + } +} + func TestTokenizationKeyUseCaseWithMetrics_Delete(t *testing.T) { tests := []struct { name string diff --git a/internal/tokenization/usecase/tokenization_key_usecase.go b/internal/tokenization/usecase/tokenization_key_usecase.go index 69e752e..45b722c 100644 --- a/internal/tokenization/usecase/tokenization_key_usecase.go +++ b/internal/tokenization/usecase/tokenization_key_usecase.go @@ -181,6 +181,22 @@ func (t *tokenizationKeyUseCase) Delete(ctx context.Context, keyID uuid.UUID) er return nil } +// GetByName retrieves a single tokenization key by its name. +// Returns the latest version for the key. Filters out soft-deleted keys. +func (t *tokenizationKeyUseCase) GetByName( + ctx context.Context, + name string, +) (*tokenizationDomain.TokenizationKey, error) { + key, err := t.tokenizationKeyRepo.GetByName(ctx, name) + if err != nil { + if apperrors.Is(err, tokenizationDomain.ErrTokenizationKeyNotFound) { + return nil, err + } + return nil, apperrors.Wrap(err, "failed to get tokenization key") + } + return key, nil +} + // ListCursor retrieves tokenization keys ordered by name ascending with cursor-based pagination. // Returns the latest version for each key name. func (t *tokenizationKeyUseCase) ListCursor( diff --git a/internal/tokenization/usecase/tokenization_key_usecase_test.go b/internal/tokenization/usecase/tokenization_key_usecase_test.go index e7142bd..3f32dd6 100644 --- a/internal/tokenization/usecase/tokenization_key_usecase_test.go +++ b/internal/tokenization/usecase/tokenization_key_usecase_test.go @@ -753,3 +753,123 @@ func TestTokenizationKeyUseCase_PurgeDeleted(t *testing.T) { assert.True(t, errors.Is(err, expectedError)) }) } + +// TestTokenizationKeyUseCase_GetByName tests the GetByName method. +func TestTokenizationKeyUseCase_GetByName(t *testing.T) { + ctx := context.Background() + + t.Run("Success_GetByName", func(t *testing.T) { + // Setup mocks + mockTxManager := databaseMocks.NewMockTxManager(t) + mockTokenizationKeyRepo := tokenizationMocks.NewMockTokenizationKeyRepository(t) + mockDekRepo := tokenizationMocks.NewMockDekRepository(t) + mockKeyManager := cryptoServiceMocks.NewMockKeyManager(t) + + // Create test data + masterKey := tokenizationTesting.CreateMasterKey() + kekChain := tokenizationTesting.CreateKekChain(masterKey) + defer kekChain.Close() + + expectedKey := &tokenizationDomain.TokenizationKey{ + ID: uuid.Must(uuid.NewV7()), + Name: "test-key", + FormatType: tokenizationDomain.FormatUUID, + Version: 1, + IsDeterministic: false, + DekID: uuid.Must(uuid.NewV7()), + } + + // Setup expectations + mockTokenizationKeyRepo.EXPECT(). + GetByName(ctx, "test-key"). + Return(expectedKey, nil). + Once() + + // Execute + uc := NewTokenizationKeyUseCase( + mockTxManager, + mockTokenizationKeyRepo, + mockDekRepo, + mockKeyManager, + kekChain, + ) + key, err := uc.GetByName(ctx, "test-key") + + // Assert + assert.NoError(t, err) + assert.Equal(t, expectedKey, key) + }) + + t.Run("Error_NotFound", func(t *testing.T) { + // Setup mocks + mockTxManager := databaseMocks.NewMockTxManager(t) + mockTokenizationKeyRepo := tokenizationMocks.NewMockTokenizationKeyRepository(t) + mockDekRepo := tokenizationMocks.NewMockDekRepository(t) + mockKeyManager := cryptoServiceMocks.NewMockKeyManager(t) + + // Create test data + masterKey := tokenizationTesting.CreateMasterKey() + kekChain := tokenizationTesting.CreateKekChain(masterKey) + defer kekChain.Close() + + expectedError := tokenizationDomain.ErrTokenizationKeyNotFound + + // Setup expectations + mockTokenizationKeyRepo.EXPECT(). + GetByName(ctx, "non-existent"). + Return(nil, expectedError). + Once() + + // Execute + uc := NewTokenizationKeyUseCase( + mockTxManager, + mockTokenizationKeyRepo, + mockDekRepo, + mockKeyManager, + kekChain, + ) + key, err := uc.GetByName(ctx, "non-existent") + + // Assert + assert.Error(t, err) + assert.Nil(t, key) + assert.True(t, errors.Is(err, expectedError)) + }) + + t.Run("Error_RepositoryFails", func(t *testing.T) { + // Setup mocks + mockTxManager := databaseMocks.NewMockTxManager(t) + mockTokenizationKeyRepo := tokenizationMocks.NewMockTokenizationKeyRepository(t) + mockDekRepo := tokenizationMocks.NewMockDekRepository(t) + mockKeyManager := cryptoServiceMocks.NewMockKeyManager(t) + + // Create test data + masterKey := tokenizationTesting.CreateMasterKey() + kekChain := tokenizationTesting.CreateKekChain(masterKey) + defer kekChain.Close() + + expectedError := errors.New("database error") + + // Setup expectations + mockTokenizationKeyRepo.EXPECT(). + GetByName(ctx, "test-key"). + Return(nil, expectedError). + Once() + + // Execute + uc := NewTokenizationKeyUseCase( + mockTxManager, + mockTokenizationKeyRepo, + mockDekRepo, + mockKeyManager, + kekChain, + ) + key, err := uc.GetByName(ctx, "test-key") + + // Assert + assert.Error(t, err) + assert.Nil(t, key) + assert.True(t, errors.Is(err, expectedError)) + assert.Contains(t, err.Error(), "failed to get tokenization key") + }) +} diff --git a/internal/transit/usecase/mocks/mocks.go b/internal/transit/usecase/mocks/mocks.go index afbdd9a..e0ec428 100644 --- a/internal/transit/usecase/mocks/mocks.go +++ b/internal/transit/usecase/mocks/mocks.go @@ -444,7 +444,7 @@ func (_c *MockTransitKeyRepository_GetByNameAndVersion_Call) Return(transitKey * return _c } -func (_c *MockTransitKeyRepository_GetByNameAndVersion_Call) RunAndReturn(run func(context.Context, string, uint) (*domain0.TransitKey, error)) *MockTransitKeyRepository_GetByNameAndVersion_Call { +func (_c *MockTransitKeyRepository_GetByNameAndVersion_Call) RunAndReturn(run func(ctx context.Context, name string, version uint) (*domain0.TransitKey, error)) *MockTransitKeyRepository_GetByNameAndVersion_Call { _c.Call.Return(run) return _c } @@ -519,8 +519,8 @@ func (_c *MockTransitKeyRepository_GetTransitKey_Call) Run(run func(ctx context. return _c } -func (_c *MockTransitKeyRepository_GetTransitKey_Call) Return(_a0 *domain0.TransitKey, _a1 domain.Algorithm, _a2 error) *MockTransitKeyRepository_GetTransitKey_Call { - _c.Call.Return(_a0, _a1, _a2) +func (_c *MockTransitKeyRepository_GetTransitKey_Call) Return(transitKey *domain0.TransitKey, algorithm domain.Algorithm, err error) *MockTransitKeyRepository_GetTransitKey_Call { + _c.Call.Return(transitKey, algorithm, err) return _c } @@ -529,7 +529,6 @@ func (_c *MockTransitKeyRepository_GetTransitKey_Call) RunAndReturn(run func(ctx return _c } -// HardDelete provides a mock function for the type MockTransitKeyRepository // HardDelete provides a mock function for the type MockTransitKeyRepository func (_mock *MockTransitKeyRepository) HardDelete(ctx context.Context, olderThan time.Time, dryRun bool) (int64, error) { ret := _mock.Called(ctx, olderThan, dryRun) @@ -857,86 +856,6 @@ func (_c *MockTransitKeyUseCase_Decrypt_Call) RunAndReturn(run func(ctx context. return _c } -// Get provides a mock function for the type MockTransitKeyUseCase -func (_mock *MockTransitKeyUseCase) Get(ctx context.Context, name string, version uint) (*domain0.TransitKey, domain.Algorithm, error) { - ret := _mock.Called(ctx, name, version) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 *domain0.TransitKey - var r1 domain.Algorithm - var r2 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint) (*domain0.TransitKey, domain.Algorithm, error)); ok { - return returnFunc(ctx, name, version) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint) *domain0.TransitKey); ok { - r0 = returnFunc(ctx, name, version) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*domain0.TransitKey) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, string, uint) domain.Algorithm); ok { - r1 = returnFunc(ctx, name, version) - } else { - r1 = ret.Get(1).(domain.Algorithm) - } - if returnFunc, ok := ret.Get(2).(func(context.Context, string, uint) error); ok { - r2 = returnFunc(ctx, name, version) - } else { - r2 = ret.Error(2) - } - return r0, r1, r2 -} - -// MockTransitKeyUseCase_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type MockTransitKeyUseCase_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - version uint -func (_e *MockTransitKeyUseCase_Expecter) Get(ctx interface{}, name interface{}, version interface{}) *MockTransitKeyUseCase_Get_Call { - return &MockTransitKeyUseCase_Get_Call{Call: _e.mock.On("Get", ctx, name, version)} -} - -func (_c *MockTransitKeyUseCase_Get_Call) Run(run func(ctx context.Context, name string, version uint)) *MockTransitKeyUseCase_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 uint - if args[2] != nil { - arg2 = args[2].(uint) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockTransitKeyUseCase_Get_Call) Return(_a0 *domain0.TransitKey, _a1 domain.Algorithm, _a2 error) *MockTransitKeyUseCase_Get_Call { - _c.Call.Return(_a0, _a1, _a2) - return _c -} - -func (_c *MockTransitKeyUseCase_Get_Call) RunAndReturn(run func(ctx context.Context, name string, version uint) (*domain0.TransitKey, domain.Algorithm, error)) *MockTransitKeyUseCase_Get_Call { - _c.Call.Return(run) - return _c -} - // Delete provides a mock function for the type MockTransitKeyUseCase func (_mock *MockTransitKeyUseCase) Delete(ctx context.Context, transitKeyID uuid.UUID) error { ret := _mock.Called(ctx, transitKeyID) @@ -1075,6 +994,84 @@ func (_c *MockTransitKeyUseCase_Encrypt_Call) RunAndReturn(run func(ctx context. } // Get provides a mock function for the type MockTransitKeyUseCase +func (_mock *MockTransitKeyUseCase) Get(ctx context.Context, name string, version uint) (*domain0.TransitKey, domain.Algorithm, error) { + ret := _mock.Called(ctx, name, version) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 *domain0.TransitKey + var r1 domain.Algorithm + var r2 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint) (*domain0.TransitKey, domain.Algorithm, error)); ok { + return returnFunc(ctx, name, version) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint) *domain0.TransitKey); ok { + r0 = returnFunc(ctx, name, version) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain0.TransitKey) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, uint) domain.Algorithm); ok { + r1 = returnFunc(ctx, name, version) + } else { + r1 = ret.Get(1).(domain.Algorithm) + } + if returnFunc, ok := ret.Get(2).(func(context.Context, string, uint) error); ok { + r2 = returnFunc(ctx, name, version) + } else { + r2 = ret.Error(2) + } + return r0, r1, r2 +} + +// MockTransitKeyUseCase_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type MockTransitKeyUseCase_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - name string +// - version uint +func (_e *MockTransitKeyUseCase_Expecter) Get(ctx interface{}, name interface{}, version interface{}) *MockTransitKeyUseCase_Get_Call { + return &MockTransitKeyUseCase_Get_Call{Call: _e.mock.On("Get", ctx, name, version)} +} + +func (_c *MockTransitKeyUseCase_Get_Call) Run(run func(ctx context.Context, name string, version uint)) *MockTransitKeyUseCase_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 uint + if args[2] != nil { + arg2 = args[2].(uint) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockTransitKeyUseCase_Get_Call) Return(transitKey *domain0.TransitKey, algorithm domain.Algorithm, err error) *MockTransitKeyUseCase_Get_Call { + _c.Call.Return(transitKey, algorithm, err) + return _c +} + +func (_c *MockTransitKeyUseCase_Get_Call) RunAndReturn(run func(ctx context.Context, name string, version uint) (*domain0.TransitKey, domain.Algorithm, error)) *MockTransitKeyUseCase_Get_Call { + _c.Call.Return(run) + return _c +} // ListCursor provides a mock function for the type MockTransitKeyUseCase func (_mock *MockTransitKeyUseCase) ListCursor(ctx context.Context, afterName *string, limit int) ([]*domain0.TransitKey, error) { From d906fd8576b920e1a10812bcf489388921c00dea Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:10:03 -0300 Subject: [PATCH 05/24] conductor(plan): Mark task 'Implement GetByName in tokenizationKeyUseCase struct' as complete --- .../tracks/tokenization_key_retrieval_20260307/plan.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index 8bd625a..47b6d57 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -6,10 +6,8 @@ This plan outlines the steps to add a new endpoint `GET /v1/tokenization/keys/:n Add the `GetByName` functionality to the use case layer. - [x] Task: Add `GetByName` to `TokenizationKeyUseCase` interface. 2fe4a7a -- [~] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. - - File: `internal/tokenization/usecase/tokenization_key_usecase.go` - - TDD: Write failing unit tests in `internal/tokenization/usecase/tokenization_key_usecase_test.go` first. -- [ ] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) +- [x] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. 3ae4bf7 +- [~] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) ## Phase 2: HTTP Layer Expose the new functionality through a REST endpoint. From 7385039aae579d8610dcbe41cffce613a720a4e5 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:18:28 -0300 Subject: [PATCH 06/24] conductor(checkpoint): Checkpoint end of Phase 1 --- conductor/tracks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conductor/tracks.md b/conductor/tracks.md index ad77f3f..b143ffd 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -4,6 +4,6 @@ This file tracks all major tracks for the project. Each track has its own detail --- -- [ ] **Track: Add Individual Key Retrieval Endpoint for tokenization module (by name)** +- [~] **Track: Add Individual Key Retrieval Endpoint for tokenization module (by name)** *Link: [./tracks/tokenization_key_retrieval_20260307/](./tracks/tokenization_key_retrieval_20260307/)* From 53faa43809a9d78dec1ed78fc867723072cf5276 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:18:40 -0300 Subject: [PATCH 07/24] conductor(plan): Mark phase 'Phase 1: Domain and Use Case Layer' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index 47b6d57..4123caf 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -2,12 +2,12 @@ This plan outlines the steps to add a new endpoint `GET /v1/tokenization/keys/:name` to retrieve a single tokenization key by its name. -## Phase 1: Domain and Use Case Layer +## Phase 1: Domain and Use Case Layer [checkpoint: 7385039] Add the `GetByName` functionality to the use case layer. - [x] Task: Add `GetByName` to `TokenizationKeyUseCase` interface. 2fe4a7a - [x] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. 3ae4bf7 -- [~] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) +- [x] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) d906fd8 ## Phase 2: HTTP Layer Expose the new functionality through a REST endpoint. From 7e55e0d8a103db9a149a99b929e3cc96c6b614ad Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:19:20 -0300 Subject: [PATCH 08/24] feat(tokenization): Add GetByNameHandler to TokenizationKeyHandler --- .../http/tokenization_key_handler.go | 25 ++++++ .../http/tokenization_key_handler_test.go | 88 +++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/internal/tokenization/http/tokenization_key_handler.go b/internal/tokenization/http/tokenization_key_handler.go index 129443f..95c20b9 100644 --- a/internal/tokenization/http/tokenization_key_handler.go +++ b/internal/tokenization/http/tokenization_key_handler.go @@ -163,6 +163,31 @@ func (h *TokenizationKeyHandler) DeleteHandler(c *gin.Context) { c.Data(http.StatusNoContent, "application/json", nil) } +// GetByNameHandler retrieves a single tokenization key by its name. +// GET /v1/tokenization/keys/:name - Requires ReadCapability. +// Returns 200 OK with key details. +func (h *TokenizationKeyHandler) GetByNameHandler(c *gin.Context) { + // Get key name from URL parameter + keyName := c.Param("name") + if keyName == "" { + httputil.HandleBadRequestGin(c, + fmt.Errorf("key name is required in URL path"), + h.logger) + return + } + + // Call use case + key, err := h.keyUseCase.GetByName(c.Request.Context(), keyName) + if err != nil { + httputil.HandleErrorGin(c, err, h.logger) + return + } + + // Map to response + response := dto.MapTokenizationKeyToResponse(key) + c.JSON(http.StatusOK, response) +} + // ListHandler retrieves tokenization keys with cursor-based pagination support. // GET /v1/tokenization/keys?after_name=key-name&limit=50 - Requires ReadCapability. // Returns 200 OK with paginated tokenization key list ordered by name ascending. diff --git a/internal/tokenization/http/tokenization_key_handler_test.go b/internal/tokenization/http/tokenization_key_handler_test.go index 68193f4..17778f0 100644 --- a/internal/tokenization/http/tokenization_key_handler_test.go +++ b/internal/tokenization/http/tokenization_key_handler_test.go @@ -470,3 +470,91 @@ func TestTokenizationKeyHandler_ListHandler(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) }) } + +func TestTokenizationKeyHandler_GetByNameHandler(t *testing.T) { + t.Run("Success_GetKeyByName", func(t *testing.T) { + handler, mockUseCase := setupTestKeyHandler(t) + + keyID := uuid.Must(uuid.NewV7()) + expectedKey := &tokenizationDomain.TokenizationKey{ + ID: keyID, + Name: "test-key", + Version: 1, + FormatType: tokenizationDomain.FormatUUID, + IsDeterministic: false, + DekID: uuid.Must(uuid.NewV7()), + CreatedAt: time.Now().UTC(), + } + + mockUseCase.EXPECT(). + GetByName(mock.Anything, "test-key"). + Return(expectedKey, nil). + Once() + + c, w := createTestContext(http.MethodGet, "/v1/tokenization/keys/test-key", nil) + c.Params = gin.Params{{Key: "name", Value: "test-key"}} + + handler.GetByNameHandler(c) + + assert.Equal(t, http.StatusOK, w.Code) + + var response dto.TokenizationKeyResponse + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, keyID.String(), response.ID) + assert.Equal(t, "test-key", response.Name) + assert.Equal(t, uint(1), response.Version) + assert.Equal(t, "uuid", response.FormatType) + assert.False(t, response.IsDeterministic) + }) + + t.Run("Error_MissingKeyNameInURL", func(t *testing.T) { + handler, _ := setupTestKeyHandler(t) + + c, w := createTestContext(http.MethodGet, "/v1/tokenization/keys/", nil) + c.Params = gin.Params{{Key: "name", Value: ""}} + + handler.GetByNameHandler(c) + + assert.Equal(t, http.StatusBadRequest, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "bad_request", response["error"]) + }) + + t.Run("Error_KeyNotFound", func(t *testing.T) { + handler, mockUseCase := setupTestKeyHandler(t) + + mockUseCase.EXPECT(). + GetByName(mock.Anything, "nonexistent-key"). + Return(nil, tokenizationDomain.ErrTokenizationKeyNotFound). + Once() + + c, w := createTestContext(http.MethodGet, "/v1/tokenization/keys/nonexistent-key", nil) + c.Params = gin.Params{{Key: "name", Value: "nonexistent-key"}} + + handler.GetByNameHandler(c) + + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("Error_UseCaseError", func(t *testing.T) { + handler, mockUseCase := setupTestKeyHandler(t) + + dbError := errors.New("database error") + + mockUseCase.EXPECT(). + GetByName(mock.Anything, "test-key"). + Return(nil, dbError). + Once() + + c, w := createTestContext(http.MethodGet, "/v1/tokenization/keys/test-key", nil) + c.Params = gin.Params{{Key: "name", Value: "test-key"}} + + handler.GetByNameHandler(c) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + }) +} From dfdb6892c410b68c6f17d008f78a5cd441357646 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:19:33 -0300 Subject: [PATCH 09/24] conductor(plan): Mark task 'Add GetByNameHandler to TokenizationKeyHandler' as complete --- .../tracks/tokenization_key_retrieval_20260307/plan.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index 4123caf..185809d 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -12,10 +12,8 @@ Add the `GetByName` functionality to the use case layer. ## Phase 2: HTTP Layer Expose the new functionality through a REST endpoint. -- [ ] Task: Add `GetByNameHandler` to `TokenizationKeyHandler`. - - File: `internal/tokenization/http/tokenization_key_handler.go` - - TDD: Write failing unit tests in `internal/tokenization/http/tokenization_key_handler_test.go` first. -- [ ] Task: Register the new route `GET /v1/tokenization/keys/:name`. +- [x] Task: Add `GetByNameHandler` to `TokenizationKeyHandler`. 7e55e0d +- [~] Task: Register the new route `GET /v1/tokenization/keys/:name`. - File: `internal/app/di_tokenization.go` - [ ] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) From b8170b6780a96e1ae575c2a0d018115b1d237dca Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:19:59 -0300 Subject: [PATCH 10/24] feat(tokenization): Register GetByName route for tokenization keys --- internal/http/server.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/http/server.go b/internal/http/server.go index 49167d0..a26a79e 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -379,6 +379,12 @@ func (s *Server) registerTokenizationRoutes( tokenizationKeyHandler.ListHandler, ) + // Get individual tokenization key + keys.GET("/:name", + authHTTP.AuthorizationMiddleware(authDomain.ReadCapability, auditLogUseCase, s.logger), + tokenizationKeyHandler.GetByNameHandler, + ) + // Create new tokenization key keys.POST("", authHTTP.AuthorizationMiddleware(authDomain.WriteCapability, auditLogUseCase, s.logger), From 50471077c4064eaeb0af94ebec2db9275b5f32ae Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:20:13 -0300 Subject: [PATCH 11/24] conductor(plan): Mark task 'Register the new route GET /v1/tokenization/keys/:name' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index 185809d..cc60ceb 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -13,9 +13,8 @@ Add the `GetByName` functionality to the use case layer. Expose the new functionality through a REST endpoint. - [x] Task: Add `GetByNameHandler` to `TokenizationKeyHandler`. 7e55e0d -- [~] Task: Register the new route `GET /v1/tokenization/keys/:name`. - - File: `internal/app/di_tokenization.go` -- [ ] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) +- [x] Task: Register the new route `GET /v1/tokenization/keys/:name`. b8170b6 +- [~] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) ## Phase 3: Integration and Documentation Ensure end-to-end functionality and update documentation. From 1f25be5364e07c8f78af2054d8d3029cef32a92f Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:24:20 -0300 Subject: [PATCH 12/24] conductor(checkpoint): Checkpoint end of Phase 2 From acdacd6226033cea1cb946aaa64fb04c103af36b Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:24:29 -0300 Subject: [PATCH 13/24] conductor(plan): Mark phase 'Phase 2: HTTP Layer' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index cc60ceb..c841826 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -9,12 +9,12 @@ Add the `GetByName` functionality to the use case layer. - [x] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. 3ae4bf7 - [x] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) d906fd8 -## Phase 2: HTTP Layer +## Phase 2: HTTP Layer [checkpoint: 1f25be5] Expose the new functionality through a REST endpoint. - [x] Task: Add `GetByNameHandler` to `TokenizationKeyHandler`. 7e55e0d - [x] Task: Register the new route `GET /v1/tokenization/keys/:name`. b8170b6 -- [~] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) +- [x] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) 5047107 ## Phase 3: Integration and Documentation Ensure end-to-end functionality and update documentation. From d5068648e2c05a4bf59cb13db7400d1c2da8a33e Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:37:29 -0300 Subject: [PATCH 14/24] test(tokenization): Add GetByName integration test --- test/integration/tokenization_flow_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/integration/tokenization_flow_test.go b/test/integration/tokenization_flow_test.go index 23f084c..a514d09 100644 --- a/test/integration/tokenization_flow_test.go +++ b/test/integration/tokenization_flow_test.go @@ -400,7 +400,27 @@ func TestIntegration_Tokenization_CompleteFlow(t *testing.T) { assert.Empty(t, body) }) - t.Logf("All 12 tokenization endpoint tests passed for %s", tc.dbDriver) + // [13/13] Test GET /v1/tokenization/keys/:name - Get tokenization key by name + t.Run("13_GetTokenizationKeyByName", func(t *testing.T) { + resp, body := ctx.makeRequest( + t, + http.MethodGet, + "/v1/tokenization/keys/"+tokenizationKeyName1, + nil, + true, + ) + assert.Equal(t, http.StatusOK, resp.StatusCode) + + var response tokenizationDTO.TokenizationKeyResponse + err := json.Unmarshal(body, &response) + require.NoError(t, err) + assert.Equal(t, tokenizationKeyName1, response.Name) + assert.Equal(t, uint(2), response.Version) // It was rotated in step 10 + assert.Equal(t, "uuid", response.FormatType) + assert.False(t, response.IsDeterministic) + }) + + t.Logf("All 13 tokenization endpoint tests passed for %s", tc.dbDriver) }) } } From 46f95451247840a637d8c6ef45e8acc3fc91a9ed Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:37:43 -0300 Subject: [PATCH 15/24] conductor(plan): Mark task 'Update integration tests' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index c841826..39ec32c 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -19,7 +19,7 @@ Expose the new functionality through a REST endpoint. ## Phase 3: Integration and Documentation Ensure end-to-end functionality and update documentation. -- [ ] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. -- [ ] Task: Update project documentation `docs/engines/tokenization.md`. +- [x] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. d506864 +- [~] Task: Update project documentation `docs/engines/tokenization.md`. - [ ] Task: Update OpenAPI specification `docs/openapi.yaml`. - [ ] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) From cca1f422f2b5ddaaa015995df18205ecd0a081ce Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:38:15 -0300 Subject: [PATCH 16/24] docs(tokenization): Document GetByName endpoint for tokenization keys --- docs/engines/tokenization.md | 41 ++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/docs/engines/tokenization.md b/docs/engines/tokenization.md index f2b5c87..7ce6605 100644 --- a/docs/engines/tokenization.md +++ b/docs/engines/tokenization.md @@ -29,9 +29,9 @@ All endpoints require `Authorization: Bearer `. - **Body**: `name`, `format_type` (`uuid`, `numeric`, `luhn-preserving`, `alphanumeric`), `is_deterministic`, `algorithm`. ```bash -curl -X POST http://localhost:8080/v1/tokenization/keys - -H "Authorization: Bearer " - -H "Content-Type: application/json" +curl -X POST http://localhost:8080/v1/tokenization/keys \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ -d '{ "name": "payment-cards", "format_type": "luhn-preserving", @@ -52,9 +52,9 @@ curl -X POST http://localhost:8080/v1/tokenization/keys - **Body**: `plaintext` (base64), `metadata` (optional object), `ttl` (optional seconds). ```bash -curl -X POST http://localhost:8080/v1/tokenization/keys/payment-cards/tokenize - -H "Authorization: Bearer " - -H "Content-Type: application/json" +curl -X POST http://localhost:8080/v1/tokenization/keys/payment-cards/tokenize \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ -d '{ "plaintext": "NDUzMjAxNTExMjgzMDM2Ng==", "metadata": { "last_four": "0366" } @@ -125,7 +125,8 @@ Example response (`200 OK`): { "id": "0194f4c1-82de-7f9a-c2b3-9def1a7bc5d8", "name": "customer-ids", - "algorithm": "uuid_v7", + "format_type": "uuid", + "algorithm": "aes-gcm", "is_deterministic": true, "version": 2, "created_at": "2026-02-27T20:10:00Z", @@ -134,7 +135,8 @@ Example response (`200 OK`): { "id": "0194f4d3-a5bc-7e2f-d8a1-4bef2c9ad7e1", "name": "payment-tokens", - "algorithm": "luhn_preserving", + "format_type": "luhn-preserving", + "algorithm": "chacha20-poly1305", "is_deterministic": false, "version": 1, "created_at": "2026-02-27T21:45:00Z", @@ -147,9 +149,30 @@ Example response (`200 OK`): **Note**: The `next_cursor` field is only present when there are more pages available. +#### Get Tokenization Key by Name + +- **Endpoint**: `GET /v1/tokenization/keys/:name` +- **Capability**: `read` +- **Success**: `200 OK` + +Example response (`200 OK`): + +```json +{ + "id": "0194f4c1-82de-7f9a-c2b3-9def1a7bc5d8", + "name": "customer-ids", + "format_type": "uuid", + "algorithm": "aes-gcm", + "is_deterministic": true, + "version": 2, + "created_at": "2026-02-27T20:10:00Z", + "updated_at": "2026-02-28T10:30:00Z" +} +``` + #### Delete Tokenization Key -- **Endpoint**: `DELETE /v1/tokenization/keys/:name` +- **Endpoint**: `DELETE /v1/tokenization/keys/:id` - **Capability**: `delete` - **Success**: `204 No Content` From fafc479e7a84b49f8f97cec54b36e96dba52d702 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:38:28 -0300 Subject: [PATCH 17/24] conductor(plan): Mark task 'Update project documentation' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index 39ec32c..6295f19 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -20,6 +20,6 @@ Expose the new functionality through a REST endpoint. Ensure end-to-end functionality and update documentation. - [x] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. d506864 -- [~] Task: Update project documentation `docs/engines/tokenization.md`. -- [ ] Task: Update OpenAPI specification `docs/openapi.yaml`. +- [x] Task: Update project documentation `docs/engines/tokenization.md`. cca1f42 +- [~] Task: Update OpenAPI specification `docs/openapi.yaml`. - [ ] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) From 17dc1ebd8e09f72f8bf1f88f71095ddc31a26a9f Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:39:35 -0300 Subject: [PATCH 18/24] docs(openapi): Add GetByName endpoint to OpenAPI specification --- docs/openapi.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 88d463c..5f88c48 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -780,6 +780,37 @@ paths: $ref: "#/components/responses/ValidationError" "429": $ref: "#/components/responses/TooManyRequests" + /v1/tokenization/keys/{name}: + parameters: + - name: name + in: path + required: true + schema: + type: string + get: + tags: [tokenization] + summary: Get tokenization key details + security: + - bearerAuth: [] + responses: + "200": + description: Tokenization key details + content: + application/json: + schema: + $ref: "#/components/schemas/TokenizationKeyResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + description: Tokenization key not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "429": + $ref: "#/components/responses/TooManyRequests" /v1/tokenization/keys/{name}/rotate: post: tags: [tokenization] From 046ad84a12f180d48e665a16de23448ab4c163bc Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:39:51 -0300 Subject: [PATCH 19/24] conductor(plan): Mark task 'Update OpenAPI specification' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index 6295f19..e811a1b 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -21,5 +21,5 @@ Ensure end-to-end functionality and update documentation. - [x] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. d506864 - [x] Task: Update project documentation `docs/engines/tokenization.md`. cca1f42 -- [~] Task: Update OpenAPI specification `docs/openapi.yaml`. -- [ ] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) +- [x] Task: Update OpenAPI specification `docs/openapi.yaml`. 17dc1eb +- [~] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) From 8ab39bd25c5f428351e31a31e1f22311bb128691 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:57:33 -0300 Subject: [PATCH 20/24] conductor(checkpoint): Checkpoint end of Phase 3 From cbccfb08d0050605beaf3a1b82367389a700eb9b Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:57:43 -0300 Subject: [PATCH 21/24] conductor(plan): Mark phase 'Phase 3: Integration and Documentation' as complete --- conductor/tracks/tokenization_key_retrieval_20260307/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md index e811a1b..ecca96e 100644 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md @@ -16,10 +16,10 @@ Expose the new functionality through a REST endpoint. - [x] Task: Register the new route `GET /v1/tokenization/keys/:name`. b8170b6 - [x] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) 5047107 -## Phase 3: Integration and Documentation +## Phase 3: Integration and Documentation [checkpoint: 8ab39bd] Ensure end-to-end functionality and update documentation. - [x] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. d506864 - [x] Task: Update project documentation `docs/engines/tokenization.md`. cca1f42 - [x] Task: Update OpenAPI specification `docs/openapi.yaml`. 17dc1eb -- [~] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) +- [x] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) 046ad84 From 2ffb18af53800a221fbe2a26467175b061c8752f Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:57:52 -0300 Subject: [PATCH 22/24] chore(conductor): Mark track 'Add Individual Key Retrieval Endpoint for tokenization module (by name)' as complete --- conductor/tracks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conductor/tracks.md b/conductor/tracks.md index b143ffd..2c57c9c 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -4,6 +4,6 @@ This file tracks all major tracks for the project. Each track has its own detail --- -- [~] **Track: Add Individual Key Retrieval Endpoint for tokenization module (by name)** +- [x] **Track: Add Individual Key Retrieval Endpoint for tokenization module (by name)** *Link: [./tracks/tokenization_key_retrieval_20260307/](./tracks/tokenization_key_retrieval_20260307/)* From 4b8f6319f27934ab3e44bfacacc6b5919fe93622 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 18:59:03 -0300 Subject: [PATCH 23/24] chore(conductor): Archive track 'Add Individual Key Retrieval Endpoint for tokenization module (by name)' --- .../index.md | 5 +++ .../metadata.json | 8 +++++ .../plan.md | 25 +++++++++++++ .../spec.md | 35 +++++++++++++++++++ conductor/tracks.md | 4 --- 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 conductor/archive/tokenization_key_retrieval_20260307/index.md create mode 100644 conductor/archive/tokenization_key_retrieval_20260307/metadata.json create mode 100644 conductor/archive/tokenization_key_retrieval_20260307/plan.md create mode 100644 conductor/archive/tokenization_key_retrieval_20260307/spec.md diff --git a/conductor/archive/tokenization_key_retrieval_20260307/index.md b/conductor/archive/tokenization_key_retrieval_20260307/index.md new file mode 100644 index 0000000..2b40a28 --- /dev/null +++ b/conductor/archive/tokenization_key_retrieval_20260307/index.md @@ -0,0 +1,5 @@ +# Track tokenization_key_retrieval_20260307 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) diff --git a/conductor/archive/tokenization_key_retrieval_20260307/metadata.json b/conductor/archive/tokenization_key_retrieval_20260307/metadata.json new file mode 100644 index 0000000..911e87d --- /dev/null +++ b/conductor/archive/tokenization_key_retrieval_20260307/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "tokenization_key_retrieval_20260307", + "type": "feature", + "status": "new", + "created_at": "2026-03-07T14:45:00Z", + "updated_at": "2026-03-07T14:45:00Z", + "description": "Add Individual Key Retrieval Endpoint for tokenization module (by name)" +} diff --git a/conductor/archive/tokenization_key_retrieval_20260307/plan.md b/conductor/archive/tokenization_key_retrieval_20260307/plan.md new file mode 100644 index 0000000..ecca96e --- /dev/null +++ b/conductor/archive/tokenization_key_retrieval_20260307/plan.md @@ -0,0 +1,25 @@ +# Implementation Plan: Tokenization Key Retrieval by Name + +This plan outlines the steps to add a new endpoint `GET /v1/tokenization/keys/:name` to retrieve a single tokenization key by its name. + +## Phase 1: Domain and Use Case Layer [checkpoint: 7385039] +Add the `GetByName` functionality to the use case layer. + +- [x] Task: Add `GetByName` to `TokenizationKeyUseCase` interface. 2fe4a7a +- [x] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. 3ae4bf7 +- [x] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) d906fd8 + +## Phase 2: HTTP Layer [checkpoint: 1f25be5] +Expose the new functionality through a REST endpoint. + +- [x] Task: Add `GetByNameHandler` to `TokenizationKeyHandler`. 7e55e0d +- [x] Task: Register the new route `GET /v1/tokenization/keys/:name`. b8170b6 +- [x] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) 5047107 + +## Phase 3: Integration and Documentation [checkpoint: 8ab39bd] +Ensure end-to-end functionality and update documentation. + +- [x] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. d506864 +- [x] Task: Update project documentation `docs/engines/tokenization.md`. cca1f42 +- [x] Task: Update OpenAPI specification `docs/openapi.yaml`. 17dc1eb +- [x] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) 046ad84 diff --git a/conductor/archive/tokenization_key_retrieval_20260307/spec.md b/conductor/archive/tokenization_key_retrieval_20260307/spec.md new file mode 100644 index 0000000..dd88cae --- /dev/null +++ b/conductor/archive/tokenization_key_retrieval_20260307/spec.md @@ -0,0 +1,35 @@ +# Specification: Tokenization Key Retrieval by Name + +## Overview +Currently, the tokenization module supports listing keys and rotating keys, but it lacks a direct endpoint to retrieve a single key's metadata by its name. This track adds a new `GET` endpoint to the tokenization key API to allow efficient lookup of individual keys. + +## Functional Requirements +- **Endpoint:** `GET /v1/tokenization/keys/:name` +- **Capability:** `tokenization:read` +- **Input:** `name` (string) as a path parameter. +- **Output:** Returns the latest version of the tokenization key metadata. +- **Status Codes:** + - `200 OK`: Key found. Returns key metadata (ID, Name, Version, FormatType, IsDeterministic, CreatedAt). + - `404 Not Found`: Key with the given name does not exist or has been soft-deleted. + - `401 Unauthorized`: Missing or invalid authentication token. + - `403 Forbidden`: Authenticated client lacks `tokenization:read` capability. + +## Non-Functional Requirements +- **Consistency:** The response format must match the existing `TokenizationKeyResponse` DTO. +- **Performance:** Direct lookup by name should be efficient (indexed in the database). + +## Acceptance Criteria +- [ ] A new method `GetByName` is added to `TokenizationKeyUseCase`. +- [ ] A new handler `GetByNameHandler` is added to `TokenizationKeyHandler`. +- [ ] The route `GET /v1/tokenization/keys/:name` is registered in the application. +- [ ] The endpoint requires the `tokenization:read` capability. +- [ ] Unit tests for the use case and handler are implemented. +- [ ] Integration tests verify the end-to-end functionality (MySQL and PostgreSQL). +- [ ] Updated integration tests in `test/integration/tokenization_flow_test.go`. +- [ ] Updated documentation: `docs/engines/tokenization.md` +- [ ] Updated OpenAPI documentation: `docs/openapi.yaml` + +## Out of Scope +- Retrieving specific versions of a key (only the latest version is returned). +- Retrieving soft-deleted keys. +- Modifying or deleting keys via this endpoint. diff --git a/conductor/tracks.md b/conductor/tracks.md index 2c57c9c..0b5c54e 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -3,7 +3,3 @@ This file tracks all major tracks for the project. Each track has its own detailed plan in its respective folder. --- - -- [x] **Track: Add Individual Key Retrieval Endpoint for tokenization module (by name)** -*Link: [./tracks/tokenization_key_retrieval_20260307/](./tracks/tokenization_key_retrieval_20260307/)* - From 39370d2a5d7b70a0521a4dad97b3a1c3e0d855e8 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 19:00:22 -0300 Subject: [PATCH 24/24] feat(tokenization): add individual key retrieval API by name Implemented a new endpoint GET /v1/tokenization/keys/:name to retrieve metadata for a single tokenization key. This addition improves API parity with the transit module and facilitates direct key inspection. Key changes: - Added GetByName to TokenizationKeyUseCase and implemented it in tokenizationKeyUseCase and its metrics decorator. - Created GetByNameHandler in TokenizationKeyHandler and registered the corresponding route. - Added comprehensive unit tests for use case and HTTP handler layers. - Added an integration test case to verify the full flow across PostgreSQL and MySQL. - Updated docs/engines/tokenization.md and docs/openapi.yaml with the new endpoint details. --- .../index.md | 5 --- .../metadata.json | 8 ----- .../plan.md | 25 ------------- .../spec.md | 35 ------------------- 4 files changed, 73 deletions(-) delete mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/index.md delete mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/metadata.json delete mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/plan.md delete mode 100644 conductor/tracks/tokenization_key_retrieval_20260307/spec.md diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/index.md b/conductor/tracks/tokenization_key_retrieval_20260307/index.md deleted file mode 100644 index 2b40a28..0000000 --- a/conductor/tracks/tokenization_key_retrieval_20260307/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Track tokenization_key_retrieval_20260307 Context - -- [Specification](./spec.md) -- [Implementation Plan](./plan.md) -- [Metadata](./metadata.json) diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/metadata.json b/conductor/tracks/tokenization_key_retrieval_20260307/metadata.json deleted file mode 100644 index 911e87d..0000000 --- a/conductor/tracks/tokenization_key_retrieval_20260307/metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "track_id": "tokenization_key_retrieval_20260307", - "type": "feature", - "status": "new", - "created_at": "2026-03-07T14:45:00Z", - "updated_at": "2026-03-07T14:45:00Z", - "description": "Add Individual Key Retrieval Endpoint for tokenization module (by name)" -} diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md b/conductor/tracks/tokenization_key_retrieval_20260307/plan.md deleted file mode 100644 index ecca96e..0000000 --- a/conductor/tracks/tokenization_key_retrieval_20260307/plan.md +++ /dev/null @@ -1,25 +0,0 @@ -# Implementation Plan: Tokenization Key Retrieval by Name - -This plan outlines the steps to add a new endpoint `GET /v1/tokenization/keys/:name` to retrieve a single tokenization key by its name. - -## Phase 1: Domain and Use Case Layer [checkpoint: 7385039] -Add the `GetByName` functionality to the use case layer. - -- [x] Task: Add `GetByName` to `TokenizationKeyUseCase` interface. 2fe4a7a -- [x] Task: Implement `GetByName` in `tokenizationKeyUseCase` struct. 3ae4bf7 -- [x] Task: Conductor - User Manual Verification 'Phase 1: Domain and Use Case Layer' (Protocol in workflow.md) d906fd8 - -## Phase 2: HTTP Layer [checkpoint: 1f25be5] -Expose the new functionality through a REST endpoint. - -- [x] Task: Add `GetByNameHandler` to `TokenizationKeyHandler`. 7e55e0d -- [x] Task: Register the new route `GET /v1/tokenization/keys/:name`. b8170b6 -- [x] Task: Conductor - User Manual Verification 'Phase 2: HTTP Layer' (Protocol in workflow.md) 5047107 - -## Phase 3: Integration and Documentation [checkpoint: 8ab39bd] -Ensure end-to-end functionality and update documentation. - -- [x] Task: Update integration tests in `test/integration/tokenization_flow_test.go`. d506864 -- [x] Task: Update project documentation `docs/engines/tokenization.md`. cca1f42 -- [x] Task: Update OpenAPI specification `docs/openapi.yaml`. 17dc1eb -- [x] Task: Conductor - User Manual Verification 'Phase 3: Integration and Documentation' (Protocol in workflow.md) 046ad84 diff --git a/conductor/tracks/tokenization_key_retrieval_20260307/spec.md b/conductor/tracks/tokenization_key_retrieval_20260307/spec.md deleted file mode 100644 index dd88cae..0000000 --- a/conductor/tracks/tokenization_key_retrieval_20260307/spec.md +++ /dev/null @@ -1,35 +0,0 @@ -# Specification: Tokenization Key Retrieval by Name - -## Overview -Currently, the tokenization module supports listing keys and rotating keys, but it lacks a direct endpoint to retrieve a single key's metadata by its name. This track adds a new `GET` endpoint to the tokenization key API to allow efficient lookup of individual keys. - -## Functional Requirements -- **Endpoint:** `GET /v1/tokenization/keys/:name` -- **Capability:** `tokenization:read` -- **Input:** `name` (string) as a path parameter. -- **Output:** Returns the latest version of the tokenization key metadata. -- **Status Codes:** - - `200 OK`: Key found. Returns key metadata (ID, Name, Version, FormatType, IsDeterministic, CreatedAt). - - `404 Not Found`: Key with the given name does not exist or has been soft-deleted. - - `401 Unauthorized`: Missing or invalid authentication token. - - `403 Forbidden`: Authenticated client lacks `tokenization:read` capability. - -## Non-Functional Requirements -- **Consistency:** The response format must match the existing `TokenizationKeyResponse` DTO. -- **Performance:** Direct lookup by name should be efficient (indexed in the database). - -## Acceptance Criteria -- [ ] A new method `GetByName` is added to `TokenizationKeyUseCase`. -- [ ] A new handler `GetByNameHandler` is added to `TokenizationKeyHandler`. -- [ ] The route `GET /v1/tokenization/keys/:name` is registered in the application. -- [ ] The endpoint requires the `tokenization:read` capability. -- [ ] Unit tests for the use case and handler are implemented. -- [ ] Integration tests verify the end-to-end functionality (MySQL and PostgreSQL). -- [ ] Updated integration tests in `test/integration/tokenization_flow_test.go`. -- [ ] Updated documentation: `docs/engines/tokenization.md` -- [ ] Updated OpenAPI documentation: `docs/openapi.yaml` - -## Out of Scope -- Retrieving specific versions of a key (only the latest version is returned). -- Retrieving soft-deleted keys. -- Modifying or deleting keys via this endpoint.