mirror of
https://github.com/Adembc/lazyssh.git
synced 2026-07-14 12:13:34 +02:00
feat: Add 60+ SSH config field support with enhanced UI (#50)
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
// Copyright 2025.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateHost(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
host string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid IP", "192.168.1.1", false},
|
||||
{"Valid hostname", "example.com", false},
|
||||
{"Valid subdomain", "api.example.com", false},
|
||||
{"Empty host", "", true},
|
||||
{"Host with spaces", "example .com", true},
|
||||
{"Host with invalid chars", "example@com", true},
|
||||
{"Host starting with dot", ".example.com", true},
|
||||
{"Host ending with dot", "example.com.", true},
|
||||
{"Host with empty label", "example..com", true},
|
||||
{"Label starting with hyphen", "-example.com", true},
|
||||
{"Label ending with hyphen", "example-.com", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateHost(tt.host)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateHost(%s) error = %v, wantErr %v", tt.host, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePortForward(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forward string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid simple forward", "8080:localhost:80", false},
|
||||
{"Valid with bind address", "127.0.0.1:8080:localhost:80", false},
|
||||
{"Multiple forwards", "8080:localhost:80, 3000:localhost:3000", false},
|
||||
{"Empty forward", "", false},
|
||||
{"Invalid format - too few parts", "8080:localhost", true},
|
||||
{"Invalid format - too many parts", "127.0.0.1:8080:localhost:80:extra", true},
|
||||
{"Invalid port number", "abc:localhost:80", true},
|
||||
{"Port out of range", "70000:localhost:80", true},
|
||||
{"Invalid bind address - malformed IP", "127.0.0.0.0.0.1:8080:localhost:80", true},
|
||||
{"Invalid bind address - IP out of range", "192.168.1.256:8080:localhost:80", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validatePortForward(tt.forward)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validatePortForward(%s) error = %v, wantErr %v", tt.forward, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDynamicForward(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forward string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid port only", "1080", false},
|
||||
{"Valid with bind address", "127.0.0.1:1080", false},
|
||||
{"Multiple forwards", "1080, 1081", false},
|
||||
{"Empty forward", "", false},
|
||||
{"Invalid format - too many parts", "127.0.0.1:1080:extra", true},
|
||||
{"Invalid port number", "abc", true},
|
||||
{"Port out of range", "70000", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateDynamicForward(tt.forward)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateDynamicForward(%s) error = %v, wantErr %v", tt.forward, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBindAddress(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
address string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid IP", "192.168.1.1", false},
|
||||
{"Valid IPv6", "::1", false},
|
||||
{"Valid hostname", "example.com", false},
|
||||
{"Wildcard", "*", false},
|
||||
{"Localhost", "localhost", false},
|
||||
{"Empty address", "", false},
|
||||
{"Address with spaces", "example .com", true},
|
||||
{"Address with invalid chars", "example@com", true},
|
||||
{"Address starting with dot", ".example.com", true},
|
||||
{"Address ending with dot", "example.com.", true},
|
||||
{"Address starting with hyphen", "-example.com", true},
|
||||
{"Address ending with hyphen", "example-.com", true},
|
||||
{"Invalid IP-like address", "127.0.0.0.0.0.1", true},
|
||||
{"Invalid numeric hostname", "192.168.1.256", true},
|
||||
{"Multiple dots", "example..com", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateBindAddress(tt.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateBindAddress(%s) error = %v, wantErr %v", tt.address, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateKeyPaths(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keys string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid single path", "~/.ssh/id_rsa", false},
|
||||
{"Valid multiple paths", "~/.ssh/id_rsa, ~/.ssh/id_ed25519", false},
|
||||
{"Empty keys", "", false},
|
||||
{"Path with newline", "~/.ssh/id_rsa\n", true},
|
||||
{"Path with tab", "~/.ssh/id_rsa\t", true},
|
||||
{"Path with carriage return", "~/.ssh/id_rsa\r", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateKeyPaths(tt.keys)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateKeyPaths(%s) error = %v, wantErr %v", tt.keys, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldValidatorPatterns(t *testing.T) {
|
||||
fieldValidators := GetFieldValidators()
|
||||
|
||||
tests := []struct {
|
||||
field string
|
||||
value string
|
||||
wantErr bool
|
||||
}{
|
||||
// Alias field
|
||||
{"Alias", "server-01", false},
|
||||
{"Alias", "server_01", false},
|
||||
{"Alias", "server.01", false},
|
||||
{"Alias", "server@01", true},
|
||||
{"Alias", "", true}, // Required field
|
||||
|
||||
// Port field
|
||||
{"Port", "22", false},
|
||||
{"Port", "65535", false},
|
||||
{"Port", "0", true},
|
||||
{"Port", "65536", true},
|
||||
{"Port", "abc", true},
|
||||
|
||||
// User field
|
||||
{"User", "root", false},
|
||||
{"User", "user_name", false},
|
||||
{"User", "user-name", false},
|
||||
{"User", "1user", true}, // Can't start with number
|
||||
|
||||
// ConnectTimeout field
|
||||
{"ConnectTimeout", "none", false},
|
||||
{"ConnectTimeout", "30", false},
|
||||
{"ConnectTimeout", "0", true},
|
||||
{"ConnectTimeout", "-10", true},
|
||||
|
||||
// IPQoS field
|
||||
{"IPQoS", "af21 cs1", false},
|
||||
{"IPQoS", "ef", false},
|
||||
{"IPQoS", "lowdelay", false},
|
||||
{"IPQoS", "invalid", true},
|
||||
|
||||
// EscapeChar field
|
||||
{"EscapeChar", "~", false},
|
||||
{"EscapeChar", "none", false},
|
||||
{"EscapeChar", "^A", false},
|
||||
{"EscapeChar", "^z", false},
|
||||
{"EscapeChar", "invalid", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.field+"_"+tt.value, func(t *testing.T) {
|
||||
validator, exists := fieldValidators[tt.field]
|
||||
if !exists {
|
||||
if tt.wantErr {
|
||||
t.Errorf("Expected validator for field %s but none found", tt.field)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
// Check required fields
|
||||
switch {
|
||||
case validator.Required && tt.value == "":
|
||||
err = &testError{msg: "required field is empty"}
|
||||
case validator.Pattern != nil && !validator.Pattern.MatchString(tt.value):
|
||||
err = &testError{msg: "pattern mismatch"}
|
||||
case validator.Validate != nil:
|
||||
err = validator.Validate(tt.value)
|
||||
}
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateField(%s, %s) error = %v, wantErr %v", tt.field, tt.value, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testError is a helper type for testing
|
||||
type testError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *testError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func TestValidationState_MultipleErrors(t *testing.T) {
|
||||
state := NewValidationState()
|
||||
|
||||
// Set multiple errors
|
||||
state.SetError("Alias", "Alias is required")
|
||||
state.SetError("Host", "Host is required")
|
||||
state.SetError("Port", "Port must be between 1 and 65535")
|
||||
state.SetError("User", "Invalid username")
|
||||
|
||||
// Check that we have errors
|
||||
if !state.HasErrors() {
|
||||
t.Error("Expected HasErrors to return true")
|
||||
}
|
||||
|
||||
// Get all errors
|
||||
errors := state.GetAllErrors()
|
||||
|
||||
// Should have 4 errors
|
||||
if len(errors) != 4 {
|
||||
t.Errorf("Expected 4 errors, got %d", len(errors))
|
||||
}
|
||||
|
||||
// Print errors for debugging
|
||||
t.Logf("Found %d errors:", len(errors))
|
||||
for i, err := range errors {
|
||||
t.Logf(" %d. %s", i+1, err)
|
||||
}
|
||||
|
||||
// Check that errors are in the expected order
|
||||
expectedOrder := []string{"Alias", "Host", "Port", "User"}
|
||||
for i, expectedField := range expectedOrder {
|
||||
if i >= len(errors) {
|
||||
break
|
||||
}
|
||||
// Check if the error message starts with the expected field name
|
||||
if len(errors[i]) < len(expectedField) || errors[i][:len(expectedField)] != expectedField {
|
||||
t.Errorf("Expected error %d to be for field %s, but got: %s", i, expectedField, errors[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidationState_Clear(t *testing.T) {
|
||||
state := NewValidationState()
|
||||
|
||||
// Add some errors
|
||||
state.SetError("Alias", "Error 1")
|
||||
state.SetError("Host", "Error 2")
|
||||
|
||||
// Clear all errors
|
||||
state.Clear()
|
||||
|
||||
// Should have no errors
|
||||
if state.HasErrors() {
|
||||
t.Error("Expected no errors after Clear()")
|
||||
}
|
||||
|
||||
if state.GetErrorCount() != 0 {
|
||||
t.Errorf("Expected error count to be 0, got %d", state.GetErrorCount())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user