Skip to content

Commit

Permalink
Fix Crud feedback, Add some much needed basic tests, small linting fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
HielkeFellinger committed Sep 6, 2024
1 parent 977be37 commit 9cb3837
Show file tree
Hide file tree
Showing 18 changed files with 473 additions and 239 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ POSTGRES_DB=go-dnd
CRYPT_COST=16
CAMPAIGN_DATA_DIR=campaign_data
CAMPAIGN_WEB_DIR=web
TEMPLATE_DIR=web/templates/
JWT_SECRET=4g85f6134i78rbc12oi343615tcfpb9
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
[![Build and Test](https://github.com/HielkeFellinger/go-dnd/actions/workflows/go.yml/badge.svg)](https://github.com/HielkeFellinger/go-dnd/actions/workflows/go.yml)
[![codecov](https://codecov.io/github/HielkeFellinger/go-dnd/graph/badge.svg?token=JXQX5TZOXE)](https://codecov.io/github/HielkeFellinger/go-dnd)

This will, most likely, be nothing more than a simple gui+engine for a D&D/TTRPG oneshot,
- Requires PostgreSql DB (Use `docker-compose.yml`)
- Run by `docker-compose.yml` or manual building source. Develop mode via [air](https://github.com/air-verse/air)

This will, most likely, be nothing more than a simple gui+engine for a ""D&D""/TTRPG oneshot,
Build for entertainment (On LAN), with friends and getting familiar with Go.
Do NOT look at this software as an example for anything.

- Security features will be used; but will not be sufficient for more than "localhost" LAN use
- Some Security features will be used; but will not be sufficient for more than "localhost" LAN use
- A Clean separation html/js/css will be done later if at all (Will look into HTMX)
- Most libraries used are chosen on nothing more than "Looks cool, I want to try that"
7 changes: 7 additions & 0 deletions app/game_engine/characterLoadEventHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package game_engine

import (
"errors"
"fmt"
"github.com/hielkefellinger/go-dnd/app/ecs"
"github.com/hielkefellinger/go-dnd/app/ecs_components"
"github.com/hielkefellinger/go-dnd/app/ecs_model_translation"
Expand All @@ -14,13 +15,19 @@ import (

func (e *baseEventMessageHandler) handleLoadCharacterEvents(message EventMessage, pool CampaignPool) error {
log.Printf("- Char. Load Event Type: '%d' Message: '%s'", message.Type, message.Id)
var handled = false

if message.Type == TypeLoadCharacters || message.Type == TypeLoadFullGame {
e.loadCharacters(message, pool)
handled = true
}
if message.Type == TypeLoadCharactersDetails {
return e.loadCharactersDetails(message, pool)
}

if !handled {
return errors.New(fmt.Sprintf("message of type '%d' is not recognised by 'handleLoadCharacterEvents()'", message.Type))
}
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion app/game_engine/characterUpdateEventHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package game_engine
import (
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/hielkefellinger/go-dnd/app/ecs"
"github.com/hielkefellinger/go-dnd/app/ecs_components"
Expand All @@ -22,7 +23,7 @@ func (e *baseEventMessageHandler) handleUpdateCharacterEvents(message EventMessa
return e.typeUpdateCharacterUsers(message, pool)
}

return nil
return errors.New(fmt.Sprintf("message of type '%d' is not recognised by 'handleUpdateCharacterEvents()'", message.Type))
}

func (e *baseEventMessageHandler) typeUpdateCharacterUsers(message EventMessage, pool CampaignPool) error {
Expand Down
5 changes: 4 additions & 1 deletion app/game_engine/chatEventHandler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package game_engine

import (
"errors"
"fmt"
"golang.org/x/net/html"
"log"
"strings"
Expand All @@ -27,10 +29,11 @@ func (e *baseEventMessageHandler) handleChatEventMessage(message EventMessage, p
if justPassTroughMessage {
// Just pass message trough
pool.TransmitEventMessage(message)
return nil
}
}

return nil
return errors.New(fmt.Sprintf("message of type '%d' is not recognised by 'handleChatEventMessage()'", message.Type))
}

func getChatMessage(message string) EventMessage {
Expand Down
105 changes: 105 additions & 0 deletions app/game_engine/engine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package game_engine

import (
"log"
"slices"
)

var leadId = "lead"
var playerOneId = "playerOne"
var playerTwoId = "playerTwo"
var playerThreeId = "playerThree"

func initTestCampaignPool() *testCampaignPool {
lead := testCampaignClient{
Id: "lead",
Lead: true,
}
playerOne := testCampaignClient{
Id: "playerOne",
Lead: false,
}
playerTwo := testCampaignClient{
Id: "playerTwo",
Lead: false,
}
playerThree := testCampaignClient{
Id: "playerThree",
Lead: false,
}

testPool := testCampaignPool{
Id: 0,
LeadId: "lead",
Clients: make(map[*testCampaignClient]bool),
Messages: make([]EventMessage, 0),
}
testPool.Engine = initTestGameEngine()
testPool.Clients[&lead] = true
testPool.Clients[&playerOne] = true
testPool.Clients[&playerTwo] = true
testPool.Clients[&playerThree] = false

return &testPool
}

func initTestGameEngine() Engine {
var baseEngine = baseEngine{}

baseEngine.World = loadGame(SpaceGameTest)
baseEngine.EventHandler = &baseEventMessageHandler{}

return &baseEngine
}

type testCampaignPool struct {
Id uint
LeadId string
Clients map[*testCampaignClient]bool
Engine Engine
Messages []EventMessage
}

type testCampaignClient struct {
Id string
Lead bool
}

func (c *testCampaignClient) GetId() string {
return c.Id
}

func (c *testCampaignClient) IsLead() bool {
return c.Lead
}

func (pool *testCampaignPool) GetId() uint {
return pool.Id
}

func (pool *testCampaignPool) GetLeadId() string {
return pool.LeadId
}

func (pool *testCampaignPool) GetEngine() Engine {
return pool.Engine
}

func (pool *testCampaignPool) TransmitEventMessage(message EventMessage) {
log.Printf("TEST Pool transmission Message: '%+v' Source: '%s' & Destination: '%v'",
message.Id, message.Source, message.Destinations)
pool.Messages = append(pool.Messages, message)
}

func (pool *testCampaignPool) GetAllClientIds(filterOut ...string) []string {
userIds := make([]string, 0)
for client := range pool.Clients {

// Filter out if applicable
if len(filterOut) > 0 && slices.Contains(filterOut, client.Id) {
continue
}
userIds = append(userIds, client.Id)
}
return userIds
}
18 changes: 16 additions & 2 deletions app/game_engine/eventHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package game_engine

import (
"bytes"
"errors"
"fmt"
"log"
"os"
"text/template"
)

Expand All @@ -16,40 +18,46 @@ type baseEventMessageHandler struct {

func (e *baseEventMessageHandler) HandleEventMessage(message EventMessage, pool CampaignPool) error {
log.Printf("Message Handler Parsing ID: '%+v' of Type: '%d' \n", message.Id, message.Type)
var handled = false

if message.Type == TypeGameSave {
err := e.handlePersistDataEvents(message, pool)
if err != nil {
return err
}
handled = true
}

if message.Type == TypeLoadFullGame || (message.Type >= TypeLoadCharacters && message.Type <= TypeLoadCharactersDetails) {
err := e.handleLoadCharacterEvents(message, pool)
if err != nil {
return err
}
handled = true
}

if message.Type == TypeLoadFullGame || (message.Type >= TypeLoadMap && message.Type <= TypeLoadMapEntity) {
err := e.handleMapLoadEvents(message, pool)
if err != nil {
return err
}
handled = true
}

if message.Type >= TypeUpdateCharacterHealth && message.Type <= TypeUpdateCharacterUsers {
err := e.handleUpdateCharacterEvents(message, pool)
if err != nil {
return err
}
handled = true
}

if message.Type >= TypeUpdateMapEntity && message.Type <= TypeChangeMapBackgroundImage {
err := e.handleMapUpdateEvents(message, pool)
if err != nil {
return err
}
handled = true
}

// Management (Overview)
Expand All @@ -58,29 +66,35 @@ func (e *baseEventMessageHandler) HandleEventMessage(message EventMessage, pool
if err != nil {
return err
}
handled = true
}
// Management (CRUD)
if message.Type > TypeManagementCrudStart && message.Type < TypeManagementCrudEnd {
err := e.handleManagementCrudEvents(message, pool)
if err != nil {
return err
}
handled = true
}

if message.Type >= TypeChatBroadcast && message.Type <= TypeChatWhisper {
err := e.handleChatEventMessage(message, pool)
if err != nil {
return err
}
handled = true
}

if !handled {
return errors.New(fmt.Sprintf("message of type '%d' is not recognised by server", message.Type))
}
return nil
}

func (e *baseEventMessageHandler) handleLoadHtmlBodyMultipleTemplateFiles(fileNames []string, templateName string, data map[string]any) string {
files := make([]string, 0)
for _, fileName := range fileNames {
files = append(files, fmt.Sprintf("web/templates/%s", fileName))
files = append(files, fmt.Sprintf(os.Getenv("TEMPLATE_DIR")+"%s", fileName))
}

var buf bytes.Buffer
Expand All @@ -94,7 +108,7 @@ func (e *baseEventMessageHandler) handleLoadHtmlBodyMultipleTemplateFiles(fileNa

func (e *baseEventMessageHandler) handleLoadHtmlBody(fileName string, templateName string, data map[string]any) string {
var buf bytes.Buffer
tmpl := template.Must(template.ParseFiles(fmt.Sprintf("web/templates/%s", fileName)))
tmpl := template.Must(template.ParseFiles(fmt.Sprintf(os.Getenv("TEMPLATE_DIR")+"%s", fileName)))
err := tmpl.ExecuteTemplate(&buf, templateName, data)
if err != nil {
log.Printf("Error parsing %s `%s`", fileName, err.Error())
Expand Down
57 changes: 57 additions & 0 deletions app/game_engine/eventHandler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package game_engine

import (
"github.com/stretchr/testify/assert"
"testing"
)

// Minimal Regression ""Test""
func TestHandleEventMessageLoadFullGame(t *testing.T) {
// Arrange
t.Setenv("TEMPLATE_DIR", "../../web/templates/")
testPool := initTestCampaignPool()
baseEventMessageHandler := baseEventMessageHandler{}
messageStartGame := NewEventMessage()
messageStartGame.Type = TypeLoadFullGame
messageStartGame.Source = playerOneId
messageStartGame.ReloadDateTime()

// Act
result := baseEventMessageHandler.HandleEventMessage(messageStartGame, testPool)

// Assert
assert.Nil(t, result) // No Error
assert.Equal(t, countEventMessageIf(testPool.Messages, func(m *EventMessage) bool {
return m.Type == TypeLoadCharacters // (501)
}), 1)
assert.Equal(t, countEventMessageIf(testPool.Messages, func(m *EventMessage) bool {
return m.Type == TypeLoadMap // (531)
}), 1)
assert.Equal(t, countEventMessageIf(testPool.Messages, func(m *EventMessage) bool {
return m.Type == TypeLoadMapEntities // (532)
}), 1)
}

func TestHandleEventMessageEmpty(t *testing.T) {
// Arrange
t.Setenv("TEMPLATE_DIR", "../../web/templates/")
testPool := initTestCampaignPool()
baseEventMessageHandler := baseEventMessageHandler{}
messageStartGame := NewEventMessage()

// Act
result := baseEventMessageHandler.HandleEventMessage(messageStartGame, testPool)

// Assert
assert.Error(t, result)
}

func countEventMessageIf(list []EventMessage, fn func(m *EventMessage) bool) int {
count := 0
for _, m := range list {
if fn(&m) {
count++
}
}
return count
}
1 change: 1 addition & 0 deletions app/game_engine/loadGame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"
)

// Minimal Regression ""Test""
func TestLoadingGame(t *testing.T) {
// Arrange

Expand Down
Loading

0 comments on commit 9cb3837

Please sign in to comment.