# Logout: End-to-End Test Pinning the --all/--everywhere Matrix

`289839f`→[main](/content/gh/entireio/cli/commits/main/index.html)·

toothbrush·1mo ago·1 file·+132 added/-0 removed

Runs the real cobra logout command against two fake entire-core servers and asserts which revoke shape each context's core receives across all four quadrants:
- logout → active context, current session
- logout --everywhere → active context, all sessions
- logout --all → every context, current session each
- logout --all --everywhere → every context, all sessions each

This pins the command-layer mapping (--everywhere → revoke-all per core) that the runLogout/runLogoutAll unit tests can't reach, since they inject the revoke func directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

7cfe9c936a83 View transcript

## Changes

1.

- cmd/entire/cli

- Mlogout_test.go +132

```go
3 unmodified lines

package cli_test

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"

"github.com/entireio/cli/cmd/entire/cli/api"
	"github.com/entireio/cli/cmd/entire/cli/auth"
	"github.com/entireio/cli/internal/entireclient/contexts"
	"github.com/entireio/cli/internal/entireclient/tokenstore"
)

const testLogoutToken = "tok123"

// coreRecorder counts the session-endpoint calls a fake entire-core sees, so the flag-matrix test can assert exactly which revoke shape each context's core received.
type coreRecorder struct {
	mu            sync.Mutex
	listCount     int
	deleteCurrent int
	deleteByID    []string
}

func (r *coreRecorder) snapshot() (list, current, byID int) {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.listCount, r.deleteCurrent, len(r.deleteByID)
}

// newCoreServer stands up a fake entire-core that answers the three session endpoints logout uses: GET (list), DELETE /current, DELETE /<id>. The list returns two sessions so --everywhere has something to delete per core.
func newCoreServer(t *testing.T) (*httptest.Server, *coreRecorder) {
	t.Helper()
	rec := &coreRecorder{}
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rec.mu.Lock()
		defer rec.mu.Unlock()
		switch {
		case r.Method == http.MethodGet && r.URL.Path == coreSessionsPath:
			rec.listCount++
			fmt.Fprint(w, `{"tokens":[{"id":"s1"},{"id":"s2"}]}`)
		case r.Method == http.MethodDelete && r.URL.Path == coreSessionsPath + "/current":
			rec.deleteCurrent++
		case r.Method == http.MethodDelete && strings.HasPrefix(r.URL.Path, coreSessionsPath + "/"):
			rec.deleteByID = append(rec.deleteByID, strings.TrimPrefix(r.URL.Path, coreSessionsPath + "/"))
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	t.Cleanup(srv.Close)
	return srv, rec
}

// seedTwoContexts records two login contexts pointing at two fake cores. The second (recB) is recorded with activate=true, so it is the *active* context — what a plain `logout` (no --all) targets.
func seedTwoContexts(t *testing.T) (recA, recB *coreRecorder) {
	t.Helper()
	cfgDir := t.TempDir()
	// Configure test environment
	t.Setenv("ENTIRE_CONFIG_DIR", cfgDir)
	restore := tokenstore.UseFileBackendForTesting(filepath.Join(t.TempDir(), "tokens.json"))
	t.Cleanup(restore)

srvA, recA := newCoreServer(t)
	srvB, recB := newCoreServer(t)
	exp := time.Now().Add(time.Hour).Unix()
	if _, err := auth.RecordLoginContext(makeContextJWT(t, fmt.Sprintf(`{"iss":%q,"handle":"alice","exp":%d}`, srvA.URL, exp)), "", true); err != nil {
		t.Fatalf("seed context A: %v", err)
	}
	if _, err := auth.RecordLoginContext(makeContextJWT(t, fmt.Sprintf(`{"iss":%q,"handle":"bob","exp":%d}`, srvB.URL, exp)), "", true); err != nil {
		t.Fatalf("seed context B: %v", err)
	}
	return recA, recB
}

// execLogout runs the real cobra logout command with --insecure-http-auth (the fake cores are http loopback) plus the given flags.
func execLogout(t *testing.T, flags ...string) {
	t.Helper()
	cmd := newLogoutCmd()
	cmd.SetArgs(append([]string{"--insecure-http-auth"}, flags...))
	var out, errOut bytes.Buffer
	cmd.SetOut(&out)
	cmd.SetErr(&errOut)
	if err := cmd.Execute(); err != nil {
		t.Fatalf("logout %v: %v (stderr=%q)", flags, err, errOut.String())
	}
}

// TestLogoutCommand_FlagMatrix pins all four quadrants of the --all/--everywhere matrix end-to-end through the cobra command, asserting which revoke shape each context's core actually received. Process-global env + keyring backend, so no t.Parallel(); subtests run sequentially, each with fresh state.
testLogoutCommand_FlagMatrix(t *testing.T) {
	t.Run("logout: active context, current session", func(t *testing.T) {
		recA, recB := seedTwoContexts(t)
		execLogout(t)
		if l, c, b := recA.snapshot(); l+c+b != 0 {
			t.Errorf("inactive context A should be untouched, got list=%d current=%d byID=%d", l, c, b)
		}
		if l, c, b := recB.snapshot(); l != 0 || c != 1 || b != 0 {
			t.Errorf("active context B: want one current-session revoke, got list=%d current=%d byID=%d", l, c, b)
		}
	})

t.Run("--everywhere: active context, all sessions", func(t *testing.T) {
		recA, recB := seedTwoContexts(t)
		execLogout(t, "--everywhere")
		if l, c, b := recA.snapshot(); l+c+b != 0 {
			t.Errorf("inactive context A should be untouched, got list=%d current=%d byID=%d", l, c, b)
		}
		if l, c, b := recB.snapshot(); l != 1 || c != 0 || b != 2 {
			t.Errorf("active context B: want list + 2 by-id revokes, got list=%d current=%d byID=%d", l, c, b)
		}
	})

t.Run("--all: every context, current session each", func(t *testing.T) {
		recA, recB := seedTwoContexts(t)
		execLogout(t, "--all")
		for name, rec := range map[string]*coreRecorder{"A": recA, "B": recB} {
			if l, c, b := rec.snapshot(); l != 0 || c != 1 || b != 0 {
				t.Errorf("context %s: want one current-session revoke, got list=%d current=%d byID=%d", name, l, c, b)
			}
		}
	})

t.Run("--all --everywhere: every context, all sessions each", func(t *testing.T) {
		recA, recB := seedTwoContexts(t)
		execLogout(t, "--all", "--everywhere")
		for name, rec := range map[string]*coreRecorder{"A": recA, "B": recB} {
			if l, c, b := rec.snapshot(); l != 1 || c != 0 || b != 2 {
				t.Errorf("context %s: want list + 2 by-id revokes, got list=%d current=%d byID=%d", name, l, c, b)
			}
		}
	})
}
```

Mcmd/entire/cli/logout_test.go +132
