Skip to content
Open
4 changes: 3 additions & 1 deletion cmd/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,9 @@ func copyFileIntoZip(path string, w io.Writer) error {
return err
}
_, err = io.Copy(w, src)
src.Close()
if closeErr := src.Close(); err == nil {
err = closeErr
}
return err
}

Expand Down
20 changes: 11 additions & 9 deletions internal/analyze/zip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ import (
"testing"
)

func TestIsGitRepo_WithDotGit(t *testing.T) {
dir := t.TempDir()
// Simulate .git via git init
if err := os.MkdirAll(filepath.Join(dir, ".git"), 0750); err != nil {
t.Fatal(err)
}
// isGitRepo uses `git rev-parse --git-dir` which needs an actual git repo;
// fall back to checking directory creation only — the factory version
// (os.Stat) is simpler, but here we just ensure non-git dir returns false.
func TestIsGitRepo_NonGitDir(t *testing.T) {
// isGitRepo uses `git rev-parse --git-dir`; an empty temp dir is not a git repo.
if isGitRepo(t.TempDir()) {
t.Error("empty temp dir should not be a git repo")
}
}

// ── isWorktreeClean ───────────────────────────────────────────────────────────

func TestIsWorktreeClean_NonGitDir(t *testing.T) {
// git status on a non-repo exits non-zero → returns false
if isWorktreeClean(t.TempDir()) {
t.Error("non-git dir should not be considered clean")
}
}

func TestWalkZip_IncludesFiles(t *testing.T) {
src := t.TempDir()
if err := os.WriteFile(filepath.Join(src, "main.go"), []byte("package main"), 0600); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ func (c *Client) request(ctx context.Context, method, path, contentType string,
return &apiErr
}
snippet := string(respBody)
if len(snippet) > 300 {
snippet = snippet[:300] + "..."
if runes := []rune(snippet); len(runes) > 300 {
snippet = string(runes[:300]) + "..."
}
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, snippet)
}
Expand Down
102 changes: 102 additions & 0 deletions internal/api/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,108 @@ func TestError_Error_WithoutCode(t *testing.T) {
}
}

// ── GraphFromShardIR ──────────────────────────────────────────────────────────

func TestGraphFromShardIR_NodesAndRels(t *testing.T) {
ir := &ShardIR{
Repo: "myorg/myrepo",
Graph: ShardGraph{
Nodes: []Node{
{ID: "n1", Labels: []string{"File"}, Properties: map[string]any{"filePath": "src/a.go"}},
{ID: "n2", Labels: []string{"Function"}, Properties: map[string]any{"name": "doThing"}},
},
Relationships: []Relationship{
{ID: "r1", Type: "defines_function", StartNode: "n1", EndNode: "n2"},
},
},
}
g := GraphFromShardIR(ir)

if len(g.Nodes) != 2 {
t.Errorf("nodes: got %d, want 2", len(g.Nodes))
}
if len(g.Relationships) != 1 {
t.Errorf("relationships: got %d, want 1", len(g.Relationships))
}
if g.Nodes[0].ID != "n1" {
t.Errorf("first node ID: got %q", g.Nodes[0].ID)
}
}

func TestGraphFromShardIR_RepoID(t *testing.T) {
ir := &ShardIR{Repo: "acme/backend"}
g := GraphFromShardIR(ir)
if got := g.RepoID(); got != "acme/backend" {
t.Errorf("RepoID: got %q, want 'acme/backend'", got)
}
}

func TestGraphFromShardIR_RelsViaRels(t *testing.T) {
// Rels() should return the Relationships slice (not Edges)
ir := &ShardIR{
Graph: ShardGraph{
Relationships: []Relationship{
{ID: "r1", Type: "imports"},
{ID: "r2", Type: "calls"},
},
},
}
g := GraphFromShardIR(ir)
rels := g.Rels()
if len(rels) != 2 {
t.Errorf("Rels(): got %d, want 2", len(rels))
}
}

func TestGraphFromShardIR_Empty(t *testing.T) {
ir := &ShardIR{}
g := GraphFromShardIR(ir)
if g == nil {
t.Fatal("GraphFromShardIR returned nil")
}
if len(g.Nodes) != 0 {
t.Errorf("empty IR: expected 0 nodes, got %d", len(g.Nodes))
}
if g.RepoID() != "" {
t.Errorf("empty IR: expected empty repoId, got %q", g.RepoID())
}
}

func TestGraphFromShardIR_NodeByID(t *testing.T) {
ir := &ShardIR{
Graph: ShardGraph{
Nodes: []Node{
{ID: "fn1", Labels: []string{"Function"}, Properties: map[string]any{"name": "myFunc"}},
},
},
}
g := GraphFromShardIR(ir)
n, ok := g.NodeByID("fn1")
if !ok {
t.Fatal("NodeByID('fn1') returned false")
}
if n.Prop("name") != "myFunc" {
t.Errorf("name prop: got %q", n.Prop("name"))
}
}

func TestGraphFromShardIR_NodesByLabel(t *testing.T) {
ir := &ShardIR{
Graph: ShardGraph{
Nodes: []Node{
{ID: "f1", Labels: []string{"File"}},
{ID: "fn1", Labels: []string{"Function"}},
{ID: "f2", Labels: []string{"File"}},
},
},
}
g := GraphFromShardIR(ir)
files := g.NodesByLabel("File")
if len(files) != 2 {
t.Errorf("NodesByLabel('File'): got %d, want 2", len(files))
}
}

func containsStr(s, sub string) bool {
return len(s) >= len(sub) && (s == sub ||
func() bool {
Expand Down
Loading
Loading