fix: updated certs and added shell int. test

This commit is contained in:
Hayden Hargreaves 2026-04-26 22:07:58 -07:00
parent 6796f16813
commit 4b6bfc8974
5 changed files with 159 additions and 21 deletions

3
examples/shell/curl.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
curl "${TERM_TAP_CURL_URL:-https://ipinfo.io:443/json}"

View File

@ -21,6 +21,7 @@ var stdoutWriter io.Writer = stdioRef{isErr: false}
var stderrWriter io.Writer = stdioRef{isErr: true}
var startSessionFn = app.StartSession
var runTUIFn = tui.Run
var currentGOOS = runtime.GOOS
type stdioRef struct {
isErr bool
@ -131,7 +132,6 @@ func runCert() {
}
certPath := ca.CertPath()
quotedCertPath := strconv.Quote(certPath)
fmt.Fprintf(stdoutWriter, "Certificate path: %s\n", certPath)
if ca.WasCreated() {
fmt.Fprintln(stdoutWriter, "Created a new local HTTPS interception CA.")
@ -148,12 +148,20 @@ func runCert() {
fmt.Fprintln(stdoutWriter, "System trust store: not trusted")
}
if runtime.GOOS != "linux" {
fmt.Fprintln(stdoutWriter, "Install this certificate into your OS or client trust store to inspect HTTPS traffic.")
return
printTrustInstructions(certPath)
}
fmt.Fprintln(stdoutWriter)
func printTrustInstructions(certPath string) {
quotedCertPath := strconv.Quote(certPath)
switch currentGOOS {
case "windows":
fmt.Fprintln(stdoutWriter, "Install this certificate into Windows Trusted Root Certification Authorities.")
fmt.Fprintln(stdoutWriter, "If curl reports Schannel revocation errors, try: curl --ssl-no-revoke --cacert "+quotedCertPath+" https://example.com")
case "darwin":
fmt.Fprintln(stdoutWriter, "Import this certificate into Keychain Access and set it to always trust.")
fmt.Fprintf(stdoutWriter, "Quick curl test: curl --proxy http://%s --cacert %s https://example.com\n", proxyAddrForPort(defaultProxyPort), quotedCertPath)
case "linux":
fmt.Fprintln(stdoutWriter, "Trust instructions (Linux):")
fmt.Fprintln(stdoutWriter, "Debian/Ubuntu:")
fmt.Fprintf(stdoutWriter, " sudo cp %s /usr/local/share/ca-certificates/termtap.crt\n", quotedCertPath)
@ -166,4 +174,7 @@ func runCert() {
fmt.Fprintln(stdoutWriter)
fmt.Fprintln(stdoutWriter, "Quick curl test:")
fmt.Fprintf(stdoutWriter, " curl --proxy http://%s --cacert %s https://example.com\n", proxyAddrForPort(defaultProxyPort), quotedCertPath)
default:
fmt.Fprintln(stdoutWriter, "Install this certificate into your OS or client trust store to inspect HTTPS traffic.")
}
}

View File

@ -206,6 +206,8 @@ func TestRunCert_EnsureCAFailureCallsFatalExit(t *testing.T) {
func TestRunCertOutputContract(t *testing.T) {
configRoot := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configRoot)
origGOOS := currentGOOS
t.Cleanup(func() { currentGOOS = origGOOS })
stdout, _ := captureOutput(t, func() {
runCert()
@ -228,6 +230,41 @@ func TestRunCertOutputContract(t *testing.T) {
}
}
func TestRunCert_WindowsHint(t *testing.T) {
configRoot := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configRoot)
origGOOS := currentGOOS
t.Cleanup(func() { currentGOOS = origGOOS })
currentGOOS = "windows"
stdout, _ := captureOutput(t, func() {
runCert()
})
if !strings.Contains(stdout, "Windows Trusted Root Certification Authorities") {
t.Fatalf("stdout missing windows trust hint: %q", stdout)
}
if !strings.Contains(stdout, "--ssl-no-revoke") {
t.Fatalf("stdout missing windows curl hint: %q", stdout)
}
}
func TestRunCert_DarwinHint(t *testing.T) {
configRoot := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configRoot)
origGOOS := currentGOOS
t.Cleanup(func() { currentGOOS = origGOOS })
currentGOOS = "darwin"
stdout, _ := captureOutput(t, func() {
runCert()
})
if !strings.Contains(stdout, "Keychain Access") {
t.Fatalf("stdout missing macOS trust hint: %q", stdout)
}
}
func TestRunCert_CreatedThenExistingMessage(t *testing.T) {
configRoot := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configRoot)

View File

@ -130,8 +130,7 @@ func (ca *CertificateAuthority) create() error {
},
NotBefore: now.Add(-1 * time.Hour),
NotAfter: now.Add(caValidFor),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 1,

View File

@ -0,0 +1,88 @@
package tui
import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"termtap.dev/internal/app"
"termtap.dev/internal/model"
)
func TestShellExampleProducesRequestData(t *testing.T) {
scriptPath := shellExamplePath(t)
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, "shell-example-ok")
}))
t.Cleanup(upstream.Close)
t.Setenv("TERM_TAP_CURL_URL", upstream.URL+"/demo")
addr := freeTCPAddr(t)
s, err := app.StartSession(model.Command{Name: "sh", Args: []string{scriptPath}}, addr)
if err != nil {
t.Fatalf("StartSession() error = %v", err)
}
t.Cleanup(s.Stop)
m := NewModel(s.Events, Controls{})
if next, _ := m.Update(tea.WindowSizeMsg{Width: 120, Height: 40}); next != nil {
m = next.(Model)
}
deadline := time.After(6 * time.Second)
for {
select {
case ev := <-s.Events:
next, _ := m.Update(EventMsg{value: ev})
m = next.(Model)
if len(m.requests) > 0 && !m.requests[0].Pending && m.requests[0].Status == http.StatusOK && string(m.requests[0].ResponseData) == "shell-example-ok" {
if got := string(m.requests[0].ResponseData); got != "shell-example-ok" {
t.Fatalf("response data = %q, want %q", got, "shell-example-ok")
}
s.Stop()
return
}
case <-deadline:
t.Fatalf("timed out waiting for request data; requests=%#v", m.requests)
}
}
}
func freeTCPAddr(t *testing.T) string {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen() error = %v", err)
}
addr := ln.Addr().String()
if err := ln.Close(); err != nil {
t.Fatalf("listener close error = %v", err)
}
return addr
}
func shellExamplePath(t *testing.T) string {
t.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("runtime.Caller failed")
}
path := filepath.Join(filepath.Dir(file), "..", "..", "examples", "shell", "curl.sh")
if _, err := os.Stat(path); err != nil {
t.Fatalf("stat shell example: %v", err)
}
return path
}