diff options
| author | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2025-11-18 19:51:02 -0800 |
|---|---|---|
| committer | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2025-11-18 19:52:02 -0800 |
| commit | 16f0a8527a0b9b736afbd4e71e6aac55e31b60e8 (patch) | |
| tree | af793d94f0bebcbc590fb7fcfe67bae81c1fbb37 /internal | |
| parent | 331edfa762e2e53fa0d5bd09fc1e4977f6c9de43 (diff) | |
Add support for GIF banners with validation and improved error messages
- Add custom validator for banner file extensions (.jpg, .jpeg, .gif)
- Update HandleTranAgreed to dynamically detect banner type from file extension
- Export FileTypeFromFilename function for use across packages
- Add comprehensive test coverage for banner validation and type detection
- Improve error messages to clearly communicate allowed file extensions
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/mobius/config.go | 19 | ||||
| -rw-r--r-- | internal/mobius/config_test.go | 95 | ||||
| -rw-r--r-- | internal/mobius/transaction_handlers.go | 3 | ||||
| -rw-r--r-- | internal/mobius/transaction_handlers_test.go | 50 |
4 files changed, 166 insertions, 1 deletions
diff --git a/internal/mobius/config.go b/internal/mobius/config.go index d04b14d..c798e54 100644 --- a/internal/mobius/config.go +++ b/internal/mobius/config.go @@ -7,6 +7,7 @@ import ( "gopkg.in/yaml.v3" "os" "path/filepath" + "strings" ) var ConfigSearchOrder = []string{ @@ -28,7 +29,25 @@ func LoadConfig(path string) (*hotline.Config, error) { } validate := validator.New() + if err = validate.RegisterValidation("bannerext", func(fl validator.FieldLevel) bool { + filename := fl.Field().String() + if filename == "" { + return true // Allow empty since BannerFile is optional + } + ext := strings.ToLower(filepath.Ext(filename)) + return ext == ".jpg" || ext == ".jpeg" || ext == ".gif" + }); err != nil { + return nil, fmt.Errorf("register validation: %v", err) + } if err = validate.Struct(config); err != nil { + // Check if this is a BannerFile validation error and provide a better message + if validationErrs, ok := err.(validator.ValidationErrors); ok { + for _, fieldErr := range validationErrs { + if fieldErr.Field() == "BannerFile" && fieldErr.Tag() == "bannerext" { + return nil, fmt.Errorf("BannerFile must have a .jpg, .jpeg, or .gif extension (got: %s)", config.BannerFile) + } + } + } return nil, fmt.Errorf("validate config: %v", err) } diff --git a/internal/mobius/config_test.go b/internal/mobius/config_test.go new file mode 100644 index 0000000..b76b16d --- /dev/null +++ b/internal/mobius/config_test.go @@ -0,0 +1,95 @@ +package mobius + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestLoadConfig_InvalidBannerFileExtension(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "mobius-config-test") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test config file with an invalid banner file extension + configContent := ` +Name: "Test Server" +Description: "Test Description" +BannerFile: "banner.png" +FileRoot: "files" +` + configPath := filepath.Join(tmpDir, "config.yaml") + if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { + t.Fatalf("Failed to write config file: %v", err) + } + + // Attempt to load the config + _, err = LoadConfig(configPath) + + // Verify that we get the improved error message + if err == nil { + t.Fatal("Expected error for invalid banner file extension, got nil") + } + + expectedMsg := "BannerFile must have a .jpg, .jpeg, or .gif extension (got: banner.png)" + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain %q, got: %v", expectedMsg, err) + } +} + +func TestLoadConfig_ValidBannerFileExtensions(t *testing.T) { + tests := []struct { + name string + bannerFile string + }{ + {"jpg extension", "banner.jpg"}, + {"jpeg extension", "banner.jpeg"}, + {"gif extension", "banner.gif"}, + {"empty banner", ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "mobius-config-test") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create files subdirectory + filesDir := filepath.Join(tmpDir, "files") + if err := os.Mkdir(filesDir, 0755); err != nil { + t.Fatalf("Failed to create files dir: %v", err) + } + + // Create a test config file with a valid banner file extension + configContent := ` +Name: "Test Server" +Description: "Test Description" +BannerFile: "` + tt.bannerFile + `" +FileRoot: "files" +` + configPath := filepath.Join(tmpDir, "config.yaml") + if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { + t.Fatalf("Failed to write config file: %v", err) + } + + // Attempt to load the config + config, err := LoadConfig(configPath) + + // Verify that we don't get a validation error + if err != nil { + t.Errorf("Expected no error for %s, got: %v", tt.bannerFile, err) + } + + if config.BannerFile != tt.bannerFile { + t.Errorf("Expected BannerFile to be %q, got %q", tt.bannerFile, config.BannerFile) + } + }) + } +} diff --git a/internal/mobius/transaction_handlers.go b/internal/mobius/transaction_handlers.go index c5b1bbb..52ea29a 100644 --- a/internal/mobius/transaction_handlers.go +++ b/internal/mobius/transaction_handlers.go @@ -1095,7 +1095,8 @@ func HandleTranAgreed(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot res = append(res, trans...) if cc.Server.Config.BannerFile != "" { - res = append(res, hotline.NewTransaction(hotline.TranServerBanner, cc.ID, hotline.NewField(hotline.FieldBannerType, []byte("JPEG")))) + bannerType := hotline.FileTypeFromFilename(cc.Server.Config.BannerFile).TypeCode + res = append(res, hotline.NewTransaction(hotline.TranServerBanner, cc.ID, hotline.NewField(hotline.FieldBannerType, []byte(bannerType)))) } res = append(res, cc.NewReply(t)) diff --git a/internal/mobius/transaction_handlers_test.go b/internal/mobius/transaction_handlers_test.go index 0d975a7..1c3fac0 100644 --- a/internal/mobius/transaction_handlers_test.go +++ b/internal/mobius/transaction_handlers_test.go @@ -2873,6 +2873,56 @@ func TestHandleTranAgreed(t *testing.T) { }, }, }, + { + name: "with gif banner", + args: args{ + cc: &hotline.ClientConn{ + Account: &hotline.Account{ + Access: func() hotline.AccessBitmap { + var bits hotline.AccessBitmap + bits.Set(hotline.AccessDisconUser) + bits.Set(hotline.AccessAnyName) + return bits + }()}, + Icon: []byte{0, 1}, + Flags: [2]byte{0, 1}, + Version: []byte{0, 1}, + ID: [2]byte{0, 1}, + Logger: NewTestLogger(), + Server: &hotline.Server{ + Config: hotline.Config{ + BannerFile: "Banner.gif", + }, + ClientMgr: func() *hotline.MockClientMgr { + m := hotline.MockClientMgr{} + m.On("List").Return([]*hotline.ClientConn{}, + ) + return &m + }(), + }, + }, + t: hotline.NewTransaction( + hotline.TranAgreed, [2]byte{}, + hotline.NewField(hotline.FieldUserName, []byte("username")), + hotline.NewField(hotline.FieldUserIconID, []byte{0, 1}), + hotline.NewField(hotline.FieldOptions, []byte{0, 0}), + ), + }, + wantRes: []hotline.Transaction{ + { + ClientID: [2]byte{0, 1}, + Type: [2]byte{0, 0x7a}, + Fields: []hotline.Field{ + hotline.NewField(hotline.FieldBannerType, []byte("GIFf")), + }, + }, + { + ClientID: [2]byte{0, 1}, + IsReply: 0x01, + Fields: []hotline.Field{}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { |