Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions internal/httputil/get_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package httputil

import (
"context"
"crypto/tls"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)

// setupInsecureTransport modifies the global DefaultTransport to allow insecure TLS
// for the duration of the test, restoring it afterwards.
func setupInsecureTransport(t *testing.T) {
t.Helper()
defaultTransport, ok := http.DefaultTransport.(*http.Transport)
if !ok {
t.Skip("DefaultTransport is not *http.Transport, cannot skip TLS verify")
}

oldConfig := defaultTransport.TLSClientConfig
if defaultTransport.TLSClientConfig == nil {
defaultTransport.TLSClientConfig = &tls.Config{}
} else {
// Clone it just to be safe if it existed
defaultTransport.TLSClientConfig = defaultTransport.TLSClientConfig.Clone()
}
defaultTransport.TLSClientConfig.InsecureSkipVerify = true

t.Cleanup(func() {
defaultTransport.TLSClientConfig = oldConfig
})
}

func TestGet_HTTPS_Enforcement(t *testing.T) {
ctx := context.Background()

// 1. Direct HTTP request should fail early
_, err := Get(ctx, "http://example.com")
if err == nil || !strings.Contains(err.Error(), "only https is allowed") {
t.Fatalf("expected insecure scheme error, got: %v", err)
}
}

func TestGet_Success(t *testing.T) {
setupInsecureTransport(t)

ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
}))
defer ts.Close()

ctx := context.Background()
body, err := Get(ctx, ts.URL)
if err != nil {
t.Fatalf("expected success, got error: %v", err)
}
defer body.Close()

b, _ := io.ReadAll(body)
if string(b) != "success" {
t.Errorf("expected body 'success', got: %s", string(b))
}
}

func TestGet_Non200Status(t *testing.T) {
setupInsecureTransport(t)

ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer ts.Close()

ctx := context.Background()
_, err := Get(ctx, ts.URL)
if err == nil || !strings.Contains(err.Error(), "unexpected status 404") {
t.Fatalf("expected 404 error, got: %v", err)
}
}

func TestGet_RedirectToHTTP(t *testing.T) {
setupInsecureTransport(t)

// Insecure server we want to redirect to
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer httpServer.Close()

// Secure server that redirects
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, httpServer.URL, http.StatusFound)
}))
defer ts.Close()

ctx := context.Background()
_, err := Get(ctx, ts.URL)
if err == nil || !strings.Contains(err.Error(), "insecure redirect: only https is allowed") {
t.Fatalf("expected insecure redirect error, got: %v", err)
}
}

func TestGet_RedirectLoop(t *testing.T) {
setupInsecureTransport(t)

var ts *httptest.Server
ts = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, ts.URL, http.StatusFound)
}))
defer ts.Close()

ctx := context.Background()
_, err := Get(ctx, ts.URL)
if err == nil || !strings.Contains(err.Error(), "stopped after 10 redirects") {
t.Fatalf("expected redirect loop error, got: %v", err)
}
}
100 changes: 100 additions & 0 deletions internal/shellgen/widgets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package shellgen

import (
"strings"
"testing"

"github.com/cheatmd-dev/cheatmd/pkg/config"
)

func setTestKeyWidget(t *testing.T, key string) {
t.Helper()
oldConfig := *config.Get()
config.Get().KeyWidget = key
t.Cleanup(func() {
*config.Get() = oldConfig
})
}

func TestBashWidget(t *testing.T) {
setTestKeyWidget(t, "\\C-g")
out := BashWidget()

if !strings.Contains(out, `bind -x '"\C-g": _cheatmd_widget'`) {
t.Errorf("BashWidget did not contain expected bind command, got:\n%s", out)
}

if !strings.Contains(out, `output="$(cheatmd --print)"`) {
t.Errorf("BashWidget missing core execution command")
}

// Test Injection edge case (no escaping implies whatever we inject will just appear)
// We just want to ensure it inserts it directly.
setTestKeyWidget(t, `"; rm -rf /; "`)
out = BashWidget()
if !strings.Contains(out, `bind -x '""; rm -rf /; "": _cheatmd_widget'`) {
t.Errorf("BashWidget did not inject keybinding as expected, got:\n%s", out)
}
}

func TestZshWidget(t *testing.T) {
setTestKeyWidget(t, "\\C-x")
out := ZshWidget()

// should translate \C-x to ^x
if !strings.Contains(out, `bindkey '^x' _cheatmd_widget`) {
t.Errorf("ZshWidget did not contain expected bindkey command, got:\n%s", out)
}

if !strings.Contains(out, `output="$(cheatmd --print --match "$input")"`) {
t.Errorf("ZshWidget missing core execution command")
}
}

func TestFishWidget(t *testing.T) {
setTestKeyWidget(t, "\\C-f")
out := FishWidget()

// should translate \C-f to \cf
if !strings.Contains(out, `bind \cf _cheatmd_widget`) {
t.Errorf("FishWidget did not contain expected bind command, got:\n%s", out)
}
}

func TestConvertToZshKey(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"\\C-g", "^g"},
{"\\C-X", "^x"},
{"^g", "^g"},
{"alt-c", "alt-c"},
}

for _, tt := range tests {
got := convertToZshKey(tt.input)
if got != tt.expected {
t.Errorf("convertToZshKey(%q) = %q, want %q", tt.input, got, tt.expected)
}
}
}

func TestConvertToFishKey(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"\\C-g", "\\cg"},
{"\\C-X", "\\cx"},
{"\\cg", "\\cg"},
{"alt-c", "alt-c"},
}

for _, tt := range tests {
got := convertToFishKey(tt.input)
if got != tt.expected {
t.Errorf("convertToFishKey(%q) = %q, want %q", tt.input, got, tt.expected)
}
}
}
Loading