package controller import ( "context" "encoding/json" "errors" "testing" "hash-of-wisdom/internal/controller/mocks" "hash-of-wisdom/internal/pow/challenge" "hash-of-wisdom/internal/protocol" "hash-of-wisdom/internal/quotes" "hash-of-wisdom/internal/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestNewWisdomController(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) assert.NotNil(t, controller) assert.Equal(t, mockService, controller.wisdomService) assert.NotNil(t, controller.codec) } func TestWisdomController_HandleMessage_UnsupportedType(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) ctx := context.Background() msg := &protocol.Message{ Type: protocol.MessageType(0xFF), // Invalid type Payload: []byte{}, } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.ErrorResponse, response.Type) var errorPayload protocol.ErrorResponsePayload err = json.Unmarshal(response.Payload, &errorPayload) require.NoError(t, err) assert.Equal(t, protocol.ErrMalformedMessage, errorPayload.Code) assert.Contains(t, errorPayload.Message, "unsupported message type: 0xff") } func TestWisdomController_HandleChallengeRequest_Success(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) // Mock successful challenge generation testChallenge := &challenge.Challenge{ Resource: "quotes", Timestamp: 12345, Difficulty: 4, Random: []byte("test"), HMAC: []byte("signature"), } mockService.On("GenerateChallenge", mock.Anything, "quotes").Return(testChallenge, nil) ctx := context.Background() msg := &protocol.Message{ Type: protocol.ChallengeRequest, Payload: []byte{}, } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.ChallengeResponse, response.Type) // Verify the challenge is properly marshaled var challengePayload challenge.Challenge err = json.Unmarshal(response.Payload, &challengePayload) require.NoError(t, err) assert.Equal(t, testChallenge.Resource, challengePayload.Resource) assert.Equal(t, testChallenge.Difficulty, challengePayload.Difficulty) mockService.AssertExpectations(t) } func TestWisdomController_HandleChallengeRequest_ServiceError(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) // Mock service error mockService.On("GenerateChallenge", mock.Anything, "quotes").Return(nil, errors.New("service error")) ctx := context.Background() msg := &protocol.Message{ Type: protocol.ChallengeRequest, Payload: []byte{}, } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.ErrorResponse, response.Type) var errorPayload protocol.ErrorResponsePayload err = json.Unmarshal(response.Payload, &errorPayload) require.NoError(t, err) assert.Equal(t, protocol.ErrServerError, errorPayload.Code) assert.Equal(t, "failed to generate challenge", errorPayload.Message) mockService.AssertExpectations(t) } func TestWisdomController_HandleSolutionRequest_Success(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) // Create test solution request testChallenge := challenge.Challenge{ Resource: "quotes", Timestamp: 12345, Difficulty: 4, Random: []byte("test"), HMAC: []byte("signature"), } solutionPayload := protocol.SolutionRequestPayload{ Challenge: testChallenge, Nonce: 12345, } payloadJSON, err := json.Marshal(solutionPayload) require.NoError(t, err) testQuote := "es.Quote{ Text: "Test quote", Author: "Test Author", } // Mock successful verification and quote retrieval mockService.On("VerifySolution", mock.Anything, mock.AnythingOfType("*challenge.Solution")).Return(nil) mockService.On("GetQuote", mock.Anything).Return(testQuote, nil) ctx := context.Background() msg := &protocol.Message{ Type: protocol.SolutionRequest, Payload: payloadJSON, } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.QuoteResponse, response.Type) // Verify the quote is properly marshaled var quotePayload quotes.Quote err = json.Unmarshal(response.Payload, "ePayload) require.NoError(t, err) assert.Equal(t, testQuote.Text, quotePayload.Text) assert.Equal(t, testQuote.Author, quotePayload.Author) mockService.AssertExpectations(t) } func TestWisdomController_HandleSolutionRequest_InvalidJSON(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) ctx := context.Background() msg := &protocol.Message{ Type: protocol.SolutionRequest, Payload: []byte("invalid json"), } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.ErrorResponse, response.Type) var errorPayload protocol.ErrorResponsePayload err = json.Unmarshal(response.Payload, &errorPayload) require.NoError(t, err) assert.Equal(t, protocol.ErrMalformedMessage, errorPayload.Code) assert.Equal(t, "invalid solution format", errorPayload.Message) mockService.AssertNotCalled(t, "VerifySolution") mockService.AssertNotCalled(t, "GetQuote") } func TestWisdomController_HandleSolutionRequest_VerificationFailed(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) // Create test solution request testChallenge := challenge.Challenge{ Resource: "quotes", Timestamp: 12345, Difficulty: 4, Random: []byte("test"), HMAC: []byte("signature"), } solutionPayload := protocol.SolutionRequestPayload{ Challenge: testChallenge, Nonce: 12345, } payloadJSON, err := json.Marshal(solutionPayload) require.NoError(t, err) // Mock verification failure mockService.On("VerifySolution", mock.Anything, mock.AnythingOfType("*challenge.Solution")).Return(service.ErrInvalidSolution) ctx := context.Background() msg := &protocol.Message{ Type: protocol.SolutionRequest, Payload: payloadJSON, } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.ErrorResponse, response.Type) var errorPayload protocol.ErrorResponsePayload err = json.Unmarshal(response.Payload, &errorPayload) require.NoError(t, err) assert.Equal(t, protocol.ErrInvalidSolution, errorPayload.Code) assert.Equal(t, "solution verification failed", errorPayload.Message) mockService.AssertExpectations(t) mockService.AssertNotCalled(t, "GetQuote") } func TestWisdomController_HandleSolutionRequest_QuoteServiceError(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) // Create test solution request testChallenge := challenge.Challenge{ Resource: "quotes", Timestamp: 12345, Difficulty: 4, Random: []byte("test"), HMAC: []byte("signature"), } solutionPayload := protocol.SolutionRequestPayload{ Challenge: testChallenge, Nonce: 12345, } payloadJSON, err := json.Marshal(solutionPayload) require.NoError(t, err) // Mock successful verification but quote service error mockService.On("VerifySolution", mock.Anything, mock.AnythingOfType("*challenge.Solution")).Return(nil) mockService.On("GetQuote", mock.Anything).Return(nil, errors.New("quote service error")) ctx := context.Background() msg := &protocol.Message{ Type: protocol.SolutionRequest, Payload: payloadJSON, } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.ErrorResponse, response.Type) var errorPayload protocol.ErrorResponsePayload err = json.Unmarshal(response.Payload, &errorPayload) require.NoError(t, err) assert.Equal(t, protocol.ErrServerError, errorPayload.Code) assert.Equal(t, "failed to get quote", errorPayload.Message) mockService.AssertExpectations(t) } func TestWisdomController_CreateErrorResponse_MarshalError(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) // This test verifies the internal error handling // In practice, json.Marshal on ErrorResponsePayload should never fail // But we test the error path for completeness response, err := controller.createErrorResponse(protocol.ErrServerError, "test message") // Should not fail with normal inputs require.NoError(t, err) assert.Equal(t, protocol.ErrorResponse, response.Type) assert.NotEmpty(t, response.Payload) } func TestWisdomController_HandleMessage_ContextCancellation(t *testing.T) { mockService := mocks.NewMockWisdomService(t) controller := NewWisdomController(mockService) // Create cancelled context ctx, cancel := context.WithCancel(context.Background()) cancel() // Mock service to respect context cancellation mockService.On("GenerateChallenge", mock.Anything, "quotes").Return(nil, context.Canceled) msg := &protocol.Message{ Type: protocol.ChallengeRequest, Payload: []byte{}, } response, err := controller.HandleMessage(ctx, msg) require.NoError(t, err) assert.Equal(t, protocol.ErrorResponse, response.Type) var errorPayload protocol.ErrorResponsePayload err = json.Unmarshal(response.Payload, &errorPayload) require.NoError(t, err) assert.Equal(t, protocol.ErrServerError, errorPayload.Code) mockService.AssertExpectations(t) }