hash-of-wisdom/internal/controller/controller_test.go

315 lines
9.4 KiB
Go

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 := &quotes.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, &quotePayload)
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)
}