325 lines
9.4 KiB
Go
325 lines
9.4 KiB
Go
|
|
package application
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"hash-of-wisdom/internal/application/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 TestNewWisdomApplication(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(mockService)
|
||
|
|
|
||
|
|
assert.NotNil(t, app)
|
||
|
|
assert.Equal(t, mockService, app.wisdomService)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleMessage_UnsupportedType(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(mockService)
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
msg := &protocol.Message{
|
||
|
|
Type: protocol.MessageType(0xFF), // Invalid type
|
||
|
|
PayloadLength: 0,
|
||
|
|
PayloadStream: nil,
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to ErrorResponse
|
||
|
|
errorResponse, ok := response.(*protocol.ErrorResponse)
|
||
|
|
require.True(t, ok, "Expected ErrorResponse")
|
||
|
|
assert.Equal(t, protocol.ErrMalformedMessage, errorResponse.Code)
|
||
|
|
assert.Contains(t, errorResponse.Message, "unsupported message type: 0xff")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleChallengeRequest_Success(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(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.ChallengeRequestType,
|
||
|
|
PayloadLength: 0,
|
||
|
|
PayloadStream: nil,
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to ChallengeResponse
|
||
|
|
challengeResponse, ok := response.(*protocol.ChallengeResponse)
|
||
|
|
require.True(t, ok, "Expected ChallengeResponse")
|
||
|
|
assert.Equal(t, testChallenge, challengeResponse.Challenge)
|
||
|
|
|
||
|
|
mockService.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleChallengeRequest_ServiceError(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(mockService)
|
||
|
|
|
||
|
|
// Mock service error
|
||
|
|
mockService.On("GenerateChallenge", mock.Anything, "quotes").Return(nil, errors.New("service error"))
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
msg := &protocol.Message{
|
||
|
|
Type: protocol.ChallengeRequestType,
|
||
|
|
PayloadLength: 0,
|
||
|
|
PayloadStream: nil,
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to ErrorResponse
|
||
|
|
errorResponse, ok := response.(*protocol.ErrorResponse)
|
||
|
|
require.True(t, ok, "Expected ErrorResponse")
|
||
|
|
assert.Equal(t, protocol.ErrServerError, errorResponse.Code)
|
||
|
|
assert.Equal(t, "Contact administrator", errorResponse.Message)
|
||
|
|
|
||
|
|
mockService.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleSolutionRequest_Success(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(mockService)
|
||
|
|
|
||
|
|
// Create test solution request
|
||
|
|
testChallenge := challenge.Challenge{
|
||
|
|
Resource: "quotes",
|
||
|
|
Timestamp: 12345,
|
||
|
|
Difficulty: 4,
|
||
|
|
Random: []byte("test"),
|
||
|
|
HMAC: []byte("signature"),
|
||
|
|
}
|
||
|
|
|
||
|
|
solutionPayload := protocol.SolutionRequest{
|
||
|
|
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.SolutionRequestType,
|
||
|
|
PayloadLength: uint32(len(payloadJSON)),
|
||
|
|
PayloadStream: bytes.NewReader(payloadJSON),
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to SolutionResponse
|
||
|
|
solutionResponse, ok := response.(*protocol.SolutionResponse)
|
||
|
|
require.True(t, ok, "Expected SolutionResponse")
|
||
|
|
assert.Equal(t, testQuote, solutionResponse.Quote)
|
||
|
|
|
||
|
|
mockService.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleSolutionRequest_InvalidJSON(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(mockService)
|
||
|
|
|
||
|
|
invalidJSON := []byte("invalid json")
|
||
|
|
ctx := context.Background()
|
||
|
|
msg := &protocol.Message{
|
||
|
|
Type: protocol.SolutionRequestType,
|
||
|
|
PayloadLength: uint32(len(invalidJSON)),
|
||
|
|
PayloadStream: bytes.NewReader(invalidJSON),
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to ErrorResponse
|
||
|
|
errorResponse, ok := response.(*protocol.ErrorResponse)
|
||
|
|
require.True(t, ok, "Expected ErrorResponse")
|
||
|
|
assert.Equal(t, protocol.ErrMalformedMessage, errorResponse.Code)
|
||
|
|
assert.Equal(t, "invalid solution format", errorResponse.Message)
|
||
|
|
|
||
|
|
mockService.AssertNotCalled(t, "VerifySolution")
|
||
|
|
mockService.AssertNotCalled(t, "GetQuote")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleSolutionRequest_VerificationFailed(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(mockService)
|
||
|
|
|
||
|
|
// Create test solution request
|
||
|
|
testChallenge := challenge.Challenge{
|
||
|
|
Resource: "quotes",
|
||
|
|
Timestamp: 12345,
|
||
|
|
Difficulty: 4,
|
||
|
|
Random: []byte("test"),
|
||
|
|
HMAC: []byte("signature"),
|
||
|
|
}
|
||
|
|
|
||
|
|
solutionPayload := protocol.SolutionRequest{
|
||
|
|
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.SolutionRequestType,
|
||
|
|
PayloadLength: uint32(len(payloadJSON)),
|
||
|
|
PayloadStream: bytes.NewReader(payloadJSON),
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to ErrorResponse
|
||
|
|
errorResponse, ok := response.(*protocol.ErrorResponse)
|
||
|
|
require.True(t, ok, "Expected ErrorResponse")
|
||
|
|
assert.Equal(t, protocol.ErrInvalidSolution, errorResponse.Code)
|
||
|
|
assert.Equal(t, "solution verification failed", errorResponse.Message)
|
||
|
|
|
||
|
|
mockService.AssertExpectations(t)
|
||
|
|
mockService.AssertNotCalled(t, "GetQuote")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleSolutionRequest_QuoteServiceError(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(mockService)
|
||
|
|
|
||
|
|
// Create test solution request
|
||
|
|
testChallenge := challenge.Challenge{
|
||
|
|
Resource: "quotes",
|
||
|
|
Timestamp: 12345,
|
||
|
|
Difficulty: 4,
|
||
|
|
Random: []byte("test"),
|
||
|
|
HMAC: []byte("signature"),
|
||
|
|
}
|
||
|
|
|
||
|
|
solutionPayload := protocol.SolutionRequest{
|
||
|
|
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.SolutionRequestType,
|
||
|
|
PayloadLength: uint32(len(payloadJSON)),
|
||
|
|
PayloadStream: bytes.NewReader(payloadJSON),
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to ErrorResponse
|
||
|
|
errorResponse, ok := response.(*protocol.ErrorResponse)
|
||
|
|
require.True(t, ok, "Expected ErrorResponse")
|
||
|
|
assert.Equal(t, protocol.ErrServerError, errorResponse.Code)
|
||
|
|
assert.Equal(t, "Contact administrator", errorResponse.Message)
|
||
|
|
|
||
|
|
mockService.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWisdomApplication_HandleMessage_ContextCancellation(t *testing.T) {
|
||
|
|
mockService := mocks.NewMockWisdomService(t)
|
||
|
|
app := NewWisdomApplication(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.ChallengeRequestType,
|
||
|
|
PayloadLength: 0,
|
||
|
|
PayloadStream: nil,
|
||
|
|
}
|
||
|
|
|
||
|
|
response, err := app.HandleMessage(ctx, msg)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Type assert to ErrorResponse
|
||
|
|
errorResponse, ok := response.(*protocol.ErrorResponse)
|
||
|
|
require.True(t, ok, "Expected ErrorResponse")
|
||
|
|
assert.Equal(t, protocol.ErrServerError, errorResponse.Code)
|
||
|
|
|
||
|
|
mockService.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestResponseEncoding(t *testing.T) {
|
||
|
|
// Test ChallengeResponse encoding produces valid binary format
|
||
|
|
testChallenge := &challenge.Challenge{
|
||
|
|
Resource: "quotes",
|
||
|
|
Timestamp: 12345,
|
||
|
|
Difficulty: 4,
|
||
|
|
Random: []byte("test"),
|
||
|
|
HMAC: []byte("signature"),
|
||
|
|
}
|
||
|
|
|
||
|
|
challengeResponse := &protocol.ChallengeResponse{Challenge: testChallenge}
|
||
|
|
var buf bytes.Buffer
|
||
|
|
err := challengeResponse.Encode(&buf)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Verify binary format
|
||
|
|
data := buf.Bytes()
|
||
|
|
assert.GreaterOrEqual(t, len(data), 5) // At least header size
|
||
|
|
|
||
|
|
// Check message type
|
||
|
|
assert.Equal(t, byte(protocol.ChallengeResponseType), data[0])
|
||
|
|
|
||
|
|
// Check payload contains expected data
|
||
|
|
payload := string(data[5:]) // Skip header
|
||
|
|
assert.Contains(t, payload, "quotes")
|
||
|
|
assert.Contains(t, payload, "12345")
|
||
|
|
}
|