From c29985b6638d02e6cd4267598676ee863380a062 Mon Sep 17 00:00:00 2001 From: typebasedio Date: Tue, 21 Apr 2026 17:02:48 -0400 Subject: [PATCH] feat(v0.4): add NewLease and NewSessionID constructors --- internal/session/lease.go | 24 +++++++++++++++++++++ internal/session/lease_test.go | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/internal/session/lease.go b/internal/session/lease.go index f2fa40f..4b57324 100644 --- a/internal/session/lease.go +++ b/internal/session/lease.go @@ -89,3 +89,27 @@ func currentTerminal() string { } return "unknown" } + +// NewSessionID returns a session identifier in the format +// "--" using the given time in UTC. +func NewSessionID(hostname string, pid int, t time.Time) string { + return fmt.Sprintf("%s-%d-%s", hostname, pid, t.UTC().Format("20060102150405")) +} + +// NewLease constructs a fresh lease for the current process. startedAt is used +// for both StartedAt and LastHeartbeatAt. +func NewLease(startedAt time.Time, agent, mode string) *Lease { + hostname := currentHostname() + pid := os.Getpid() + return &Lease{ + SessionID: NewSessionID(hostname, pid, startedAt), + PID: pid, + Hostname: hostname, + Username: currentUsername(), + Agent: agent, + Mode: mode, + StartedAt: startedAt.UTC().Truncate(time.Second), + LastHeartbeatAt: startedAt.UTC().Truncate(time.Second), + Terminal: currentTerminal(), + } +} diff --git a/internal/session/lease_test.go b/internal/session/lease_test.go index 8394ac2..8a73ba4 100644 --- a/internal/session/lease_test.go +++ b/internal/session/lease_test.go @@ -100,3 +100,42 @@ func TestReadLeaseMissingFileReturnsErrNotExist(t *testing.T) { t.Errorf("expected os.ErrNotExist, got %v", err) } } + +func TestNewSessionIDFormat(t *testing.T) { + now := time.Date(2026, 4, 21, 14, 30, 22, 0, time.UTC) + id := NewSessionID("warren-desktop", 12345, now) + want := "warren-desktop-12345-20260421143022" + if id != want { + t.Errorf("NewSessionID = %q, want %q", id, want) + } +} + +func TestNewLeasePopulatesIdentity(t *testing.T) { + now := time.Date(2026, 4, 21, 14, 30, 22, 0, time.UTC) + l := NewLease(now, "claude", "local") + + if l.StartedAt != now { + t.Errorf("StartedAt: got %v, want %v", l.StartedAt, now) + } + if l.LastHeartbeatAt != now { + t.Errorf("LastHeartbeatAt should be initialized to StartedAt, got %v", l.LastHeartbeatAt) + } + if l.Agent != "claude" { + t.Errorf("Agent: got %q, want %q", l.Agent, "claude") + } + if l.Mode != "local" { + t.Errorf("Mode: got %q, want %q", l.Mode, "local") + } + if l.PID != os.Getpid() { + t.Errorf("PID: got %d, want %d", l.PID, os.Getpid()) + } + if l.Hostname == "" { + t.Error("Hostname should not be empty") + } + if l.Username == "" { + t.Error("Username should not be empty") + } + if !strings.Contains(l.SessionID, l.Hostname) { + t.Errorf("SessionID %q should contain hostname %q", l.SessionID, l.Hostname) + } +}