| package main | |
| import ( | |
| "encoding/json" | |
| "io" | |
| "net/http" | |
| "net/http/httptest" | |
| "os" | |
| "path/filepath" | |
| "testing" | |
| ) | |
| // TestParseArgs verifies that value-flag-aware parser correctly separates | |
| // boolean flags, value flags, and positional arguments. | |
| func TestParseArgs(t *testing.T) { | |
| // Case 1: Deploy with value flag and boolean flags | |
| args := []string{"deploy", "--file", "tool.html", "--private", "--pro"} | |
| parsed := parseArgs(args) | |
| cmd := "" | |
| if len(parsed.Positional) > 0 { | |
| cmd = parsed.Positional[0] | |
| } | |
| if cmd != "deploy" { | |
| t.Errorf("Expected command 'deploy', got '%s'", cmd) | |
| } | |
| if parsed.Get("--file", "-f") != "tool.html" { | |
| t.Errorf("Expected --file value 'tool.html', got '%s'", parsed.Get("--file", "-f")) | |
| } | |
| if !parsed.Has("--private", "") { | |
| t.Errorf("Expected --private boolean flag to be true") | |
| } | |
| if !parsed.Has("--pro", "") { | |
| t.Errorf("Expected --pro boolean flag to be true") | |
| } | |
| // Case 2: Positional arguments shouldn't be consumed as values for boolean flags | |
| args2 := []string{"deploy", "--public", "some-extra-arg.html"} | |
| parsed2 := parseArgs(args2) | |
| if !parsed2.Has("--public", "") { | |
| t.Errorf("Expected --public to be present") | |
| } | |
| // 'some-extra-arg.html' should be treated as a positional arg, not the value of --public | |
| foundPositional := false | |
| for _, p := range parsed2.Positional { | |
| if p == "some-extra-arg.html" { | |
| foundPositional = true | |
| } | |
| } | |
| if !foundPositional { | |
| t.Errorf("Expected 'some-extra-arg.html' to be a positional argument, but was ignored or consumed") | |
| } | |
| } | |
| // TestTruncateOrPad verifies rune padding and length limits | |
| func TestTruncateOrPad(t *testing.T) { | |
| s1 := truncateOrPad("hello", 10) | |
| if len(s1) != 10 { | |
| t.Errorf("Expected padded string length 10, got %d", len(s1)) | |
| } | |
| s2 := truncateOrPad("this is a very long string", 5) | |
| if s2 != "this…" { | |
| t.Errorf("Expected truncated string 'this…', got '%s'", s2) | |
| } | |
| // Rune-aware checks (non-ASCII characters) | |
| s3 := truncateOrPad("سلام", 6) | |
| // سلام has 4 runes, should be padded with 2 spaces | |
| if len(s3) != 10 { // length of runes/bytes might differ, let's verify character count | |
| runes := []rune(s3) | |
| if len(runes) != 6 { | |
| t.Errorf("Expected 6 runes, got %d in '%s'", len(runes), s3) | |
| } | |
| } | |
| } | |
| // TestFormatDate verifies ISO timezone formatting | |
| func TestFormatDate(t *testing.T) { | |
| res1 := formatDate("2026-07-11T11:15:00Z") | |
| if res1 == "—" { | |
| t.Errorf("Expected formatted date, got default empty sign") | |
| } | |
| res2 := formatDate(12345) // invalid format | |
| if res2 != "—" { | |
| t.Errorf("Expected '—' for invalid timestamp types, got '%s'", res2) | |
| } | |
| } | |
| // TestConfigStore verifies loadConfig and saveConfig file permissions (0600) | |
| func TestConfigStore(t *testing.T) { | |
| tempDir, err := os.MkdirTemp("", "ie-cli-test") | |
| if err != nil { | |
| t.Fatalf("Failed to create temp dir: %v", err) | |
| } | |
| defer os.RemoveAll(tempDir) | |
| // Mock getConfigPath to write to tempDir | |
| originalHome := os.Getenv("HOME") | |
| os.Setenv("HOME", tempDir) | |
| defer os.Setenv("HOME", originalHome) | |
| cfg := &Config{ | |
| Token: "test-jwt-token-xyz", | |
| Username: "tahamajs", | |
| Host: "http://localhost:9092", | |
| } | |
| err = saveConfig(cfg) | |
| if err != nil { | |
| t.Fatalf("Failed to save config: %v", err) | |
| } | |
| // Verify file permission is 0600 | |
| cfgPath := filepath.Join(tempDir, ".ie-research-cli.json") | |
| info, err := os.Stat(cfgPath) | |
| if err != nil { | |
| t.Fatalf("Failed to stat config file: %v", err) | |
| } | |
| mode := info.Mode().Perm() | |
| if mode != 0600 { | |
| t.Errorf("Expected config file permission 0600, got %o", mode) | |
| } | |
| // Load and verify fields | |
| loaded, err := loadConfig() | |
| if err != nil { | |
| t.Fatalf("Failed to load config: %v", err) | |
| } | |
| if loaded.Username != "tahamajs" || loaded.Token != "test-jwt-token-xyz" { | |
| t.Errorf("Loaded config values mismatch: username=%s, token=%s", loaded.Username, loaded.Token) | |
| } | |
| } | |
| // TestFetchAPI verifies integration with REST APIs | |
| func TestFetchAPI(t *testing.T) { | |
| // Create mock HTTP server simulating Spring Boot REST endpoints | |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| // Verify method and headers | |
| if r.Method != "POST" { | |
| t.Errorf("Expected POST request, got %s", r.Method) | |
| } | |
| if auth := r.Header.Get("Authorization"); auth != "Bearer dummy-token" { | |
| t.Errorf("Expected Bearer Authorization header, got '%s'", auth) | |
| } | |
| // Read and verify body | |
| bodyBytes, _ := io.ReadAll(r.Body) | |
| var payload map[string]interface{} | |
| json.Unmarshal(bodyBytes, &payload) | |
| if payload["name"] != "Bibtex Converter" { | |
| t.Errorf("Expected tool name 'Bibtex Converter', got '%v'", payload["name"]) | |
| } | |
| // Return success response | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(`{"success":true,"toolId":42,"message":"Deployed successfully"}`)) | |
| })) | |
| defer server.Close() | |
| payload := map[string]interface{}{ | |
| "name": "Bibtex Converter", | |
| "version": "1.0.0", | |
| } | |
| resp, err := fetchAPI("POST", server.URL, payload, "dummy-token") | |
| if err != nil { | |
| t.Fatalf("fetchAPI failed: %v", err) | |
| } | |
| if resp["success"] != true || resp["toolId"].(float64) != 42 { | |
| t.Errorf("Expected response toolId=42 and success=true, got: %v", resp) | |
| } | |
| } | |
Xet Storage Details
- Size:
- 5.2 kB
- Xet hash:
- 2a0080dc11bd99ebe495f860e297ea120b1b9fc62a2268835cbc162a466b3192
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.