fix(import): require a stored token, not just a context, for logged-in check · Entire

fix(import): require a stored token, not just a context, for logged-in check

fbf2abf

Addresses Copilot's review on #1774: a contexts.json entry can exist without a usable credential (partially-removed context, missing keychain token), which made importLoggedIn suppress the sync notice even though the import can't sync. Now the current context must also yield a stored token (local read via auth.LoginTokenForContext — no network).

This stays a presence check, not a liveness check: LoginTokenForContext returns a present-but-expired token without error, and verifying usability needs a network refresh we deliberately avoid on the import path. That narrow residual false-negative (expired token) is accepted to keep the check local and prompt-free; the common broken case is now handled. Adds a test for the context-present-but-token-missing case.

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

Changes

2

// Local auth reads, as package vars so the login heuristic's branching is
// testable without a real keyring or config dir. Production wiring is the real
// auth functions.
var (
    importListContexts    = auth.Contexts
    importTokenForContext = auth.LoginTokenForContext
)

// importLoggedIn reports whether there is an active login the imported history
// could eventually sync under: an ENTIRE_TOKEN env token, or a current stored
// login context. It is local-only (reads env + contexts.json) and never makes a
// network call, so it is safe on the import path. It is a package var so tests
// can force either state.
var importLoggedIn = func() bool {
    if os.Getenv(auth.EnvTokenVar) != "" {
        return true
    }
    ctxs, current, err := auth.Contexts()
    return err == nil && current != "" && len(ctxs) > 0
    ctxs, current, err := importListContexts()
    if err != nil || current == "" {
        return false
    }
    for _, c := range ctxs {
        if c.Name == current {
            tok, terr := importTokenForContext(c)
            return terr == nil && tok != ""
        }
    }
    return false
}

warnIfImportNotSynced

// warnIfImportNotSynced prints a one-time notice, when the user is not logged

TestWarnIfImportNotSynced

import (
    "bytes"
    "errors"
    "strings"
    "testing"

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

func TestWarnIfImportNotSynced(t *testing.T) {
    // Test implementation here
}

// TestImportLoggedIn exercises the default login heuristic's branching via the
// local-read seams.
func TestImportLoggedIn(t *testing.T) {
    origCtx, origTok := importListContexts, importTokenForContext
    t.Cleanup(func() { importListContexts, importTokenForContext = origCtx, origTok })
    // Ensure no env token leaks in from the environment for the context cases.
    t.Setenv(auth.EnvTokenVar, "")

withCurrent := func() ([]*contexts.Context, string, error) {
        return []*contexts.Context{{Name: "prod"}}, "prod", nil
    }

t.Run("current context with a stored token is logged in", func(t *testing.T) {
        importListContexts = withCurrent
        importTokenForContext = func(*contexts.Context) (string, error) { return "stored-token", nil }
        if !importLoggedIn() {
            t.Fatal("context with a token should count as logged in")
        }
    })

t.Run("current context with a missing token is NOT logged in", func(t *testing.T) {
        importListContexts = withCurrent
        importTokenForContext = func(*contexts.Context) (string, error) {
            return "", errors.New("no token stored")
        }
        if importLoggedIn() {
            t.Fatal("context present but token missing must not count as logged in")
        }
    })

t.Run("no current context is not logged in", func(t *testing.T) {
        importListContexts = func() ([]*contexts.Context, string, error) { return nil, "", nil }
        importTokenForContext = func(*contexts.Context) (string, error) { return "stored-token", nil }
        if importLoggedIn() {
            t.Fatal("no current context should not count as logged in")
        }
    })

t.Run("env token counts as logged in even with no context", func(t *testing.T) {
        t.Setenv(auth.EnvTokenVar, "env-token")
        importListContexts = func() ([]*contexts.Context, string, error) { return nil, "", nil }
        if !importLoggedIn() {
            t.Fatal("ENTIRE_TOKEN should count as logged in")
        }
    })
}