fix: updated certs and added shell int. test
This commit is contained in:
parent
6796f16813
commit
4b6bfc8974
3
examples/shell/curl.sh
Executable file
3
examples/shell/curl.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
curl "${TERM_TAP_CURL_URL:-https://ipinfo.io:443/json}"
|
||||||
@ -21,6 +21,7 @@ var stdoutWriter io.Writer = stdioRef{isErr: false}
|
|||||||
var stderrWriter io.Writer = stdioRef{isErr: true}
|
var stderrWriter io.Writer = stdioRef{isErr: true}
|
||||||
var startSessionFn = app.StartSession
|
var startSessionFn = app.StartSession
|
||||||
var runTUIFn = tui.Run
|
var runTUIFn = tui.Run
|
||||||
|
var currentGOOS = runtime.GOOS
|
||||||
|
|
||||||
type stdioRef struct {
|
type stdioRef struct {
|
||||||
isErr bool
|
isErr bool
|
||||||
@ -131,7 +132,6 @@ func runCert() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
certPath := ca.CertPath()
|
certPath := ca.CertPath()
|
||||||
quotedCertPath := strconv.Quote(certPath)
|
|
||||||
fmt.Fprintf(stdoutWriter, "Certificate path: %s\n", certPath)
|
fmt.Fprintf(stdoutWriter, "Certificate path: %s\n", certPath)
|
||||||
if ca.WasCreated() {
|
if ca.WasCreated() {
|
||||||
fmt.Fprintln(stdoutWriter, "Created a new local HTTPS interception CA.")
|
fmt.Fprintln(stdoutWriter, "Created a new local HTTPS interception CA.")
|
||||||
@ -148,12 +148,20 @@ func runCert() {
|
|||||||
fmt.Fprintln(stdoutWriter, "System trust store: not trusted")
|
fmt.Fprintln(stdoutWriter, "System trust store: not trusted")
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS != "linux" {
|
printTrustInstructions(certPath)
|
||||||
fmt.Fprintln(stdoutWriter, "Install this certificate into your OS or client trust store to inspect HTTPS traffic.")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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, "Trust instructions (Linux):")
|
||||||
fmt.Fprintln(stdoutWriter, "Debian/Ubuntu:")
|
fmt.Fprintln(stdoutWriter, "Debian/Ubuntu:")
|
||||||
fmt.Fprintf(stdoutWriter, " sudo cp %s /usr/local/share/ca-certificates/termtap.crt\n", quotedCertPath)
|
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)
|
||||||
fmt.Fprintln(stdoutWriter, "Quick curl test:")
|
fmt.Fprintln(stdoutWriter, "Quick curl test:")
|
||||||
fmt.Fprintf(stdoutWriter, " curl --proxy http://%s --cacert %s https://example.com\n", proxyAddrForPort(defaultProxyPort), quotedCertPath)
|
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.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,6 +206,8 @@ func TestRunCert_EnsureCAFailureCallsFatalExit(t *testing.T) {
|
|||||||
func TestRunCertOutputContract(t *testing.T) {
|
func TestRunCertOutputContract(t *testing.T) {
|
||||||
configRoot := t.TempDir()
|
configRoot := t.TempDir()
|
||||||
t.Setenv("XDG_CONFIG_HOME", configRoot)
|
t.Setenv("XDG_CONFIG_HOME", configRoot)
|
||||||
|
origGOOS := currentGOOS
|
||||||
|
t.Cleanup(func() { currentGOOS = origGOOS })
|
||||||
|
|
||||||
stdout, _ := captureOutput(t, func() {
|
stdout, _ := captureOutput(t, func() {
|
||||||
runCert()
|
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) {
|
func TestRunCert_CreatedThenExistingMessage(t *testing.T) {
|
||||||
configRoot := t.TempDir()
|
configRoot := t.TempDir()
|
||||||
t.Setenv("XDG_CONFIG_HOME", configRoot)
|
t.Setenv("XDG_CONFIG_HOME", configRoot)
|
||||||
|
|||||||
@ -130,8 +130,7 @@ func (ca *CertificateAuthority) create() error {
|
|||||||
},
|
},
|
||||||
NotBefore: now.Add(-1 * time.Hour),
|
NotBefore: now.Add(-1 * time.Hour),
|
||||||
NotAfter: now.Add(caValidFor),
|
NotAfter: now.Add(caValidFor),
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
MaxPathLen: 1,
|
MaxPathLen: 1,
|
||||||
|
|||||||
88
internal/tui/shell_integration_test.go
Normal file
88
internal/tui/shell_integration_test.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user