clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

chore/regenerate-vhs-gifs
puzzletea / cmd / new_export_test.go
new_export_test.go 5.7K
1package cmd23import (4 "bytes"5 "encoding/json"6 "os"7 "path/filepath"8 "strconv"9 "strings"10 "testing"11 "time"1213 "github.com/FelineStateMachine/puzzletea/pdfexport"1415 "github.com/spf13/cobra"16)1718func TestRunNewExportRejectsUnsupportedGame(t *testing.T) {19 withExportFlagReset(t)20 output := filepath.Join(t.TempDir(), "lights.jsonl")21 flagOutput = output2223 cmd, _ := newExportTestCmd(t, false)24 err := runNewExport(cmd, []string{"lights-out"})25 if err == nil {26 t.Fatal("expected unsupported export error")27 }28 if !strings.Contains(err.Error(), "does not support export") {29 t.Fatalf("unexpected error: %v", err)30 }31 if _, statErr := os.Stat(output); !os.IsNotExist(statErr) {32 t.Fatalf("expected no output file, stat err = %v", statErr)33 }34}3536func TestRunNewExportValidation(t *testing.T) {37 t.Run("writes jsonl to stdout when output omitted", func(t *testing.T) {38 withExportFlagReset(t)39 flagExport = 24041 cmd, out := newExportTestCmd(t, true)42 err := runNewExport(cmd, []string{"nonogram", "mini"})43 if err != nil {44 t.Fatalf("expected stdout export success, got error: %v", err)45 }4647 lines := strings.Split(strings.TrimSpace(out.String()), "\n")48 if got, want := len(lines), 2; got != want {49 t.Fatalf("jsonl lines = %d, want %d", got, want)50 }51 for i, line := range lines {52 var record pdfexport.JSONLRecord53 if err := json.Unmarshal([]byte(line), &record); err != nil {54 t.Fatalf("line %d is not valid jsonl: %v", i+1, err)55 }56 if record.Schema != pdfexport.ExportSchemaV1 {57 t.Fatalf("line %d schema = %q, want %q", i+1, record.Schema, pdfexport.ExportSchemaV1)58 }59 }60 })6162 t.Run("output extension must be jsonl", func(t *testing.T) {63 withExportFlagReset(t)64 flagOutput = filepath.Join(t.TempDir(), "out.txt")6566 cmd, _ := newExportTestCmd(t, false)67 err := runNewExport(cmd, []string{"nonogram", "mini"})68 if err == nil {69 t.Fatal("expected extension validation error")70 }71 if !strings.Contains(err.Error(), ".jsonl extension") {72 t.Fatalf("unexpected error: %v", err)73 }74 })7576 t.Run("set-seed cannot be combined with output", func(t *testing.T) {77 withExportFlagReset(t)78 flagSetSeed = "abc"79 flagOutput = filepath.Join(t.TempDir(), "out.jsonl")8081 cmd, _ := newExportTestCmd(t, false)82 err := runNewExport(cmd, []string{"nonogram", "mini"})83 if err == nil {84 t.Fatal("expected set-seed validation error")85 }86 if !strings.Contains(err.Error(), "--set-seed cannot be combined with export (--export/--output)") {87 t.Fatalf("unexpected error: %v", err)88 }89 })90}9192func TestRunNewExportReproducibleWithSeed(t *testing.T) {93 withExportFlagReset(t)9495 fixedNow := time.Date(2026, 2, 22, 11, 0, 0, 0, time.UTC)96 previousNow := exportNow97 exportNow = func() time.Time { return fixedNow }98 t.Cleanup(func() { exportNow = previousNow })99100 fileA := filepath.Join(t.TempDir(), "a.jsonl")101 fileB := filepath.Join(t.TempDir(), "b.jsonl")102103 flagExport = 3104 flagWithSeed = "zine-seed-01"105 flagOutput = fileA106 cmdA, _ := newExportTestCmd(t, false)107 if err := runNewExport(cmdA, []string{"nonogram", "mini"}); err != nil {108 t.Fatalf("first export failed: %v", err)109 }110111 flagOutput = fileB112 cmdB, _ := newExportTestCmd(t, false)113 if err := runNewExport(cmdB, []string{"nonogram", "mini"}); err != nil {114 t.Fatalf("second export failed: %v", err)115 }116117 a, err := os.ReadFile(fileA)118 if err != nil {119 t.Fatal(err)120 }121 b, err := os.ReadFile(fileB)122 if err != nil {123 t.Fatal(err)124 }125 if string(a) != string(b) {126 t.Fatal("expected deterministic jsonl output for identical seed and args")127 }128}129130func TestRunNewExportOverwritesOutputFile(t *testing.T) {131 withExportFlagReset(t)132133 file := filepath.Join(t.TempDir(), "out.jsonl")134 if err := os.WriteFile(file, []byte("old"), 0o644); err != nil {135 t.Fatal(err)136 }137138 flagExport = 1139 flagWithSeed = "overwrite-seed"140 flagOutput = file141142 cmd, _ := newExportTestCmd(t, false)143 if err := runNewExport(cmd, []string{"nonogram", "mini"}); err != nil {144 t.Fatalf("export failed: %v", err)145 }146147 data, err := os.ReadFile(file)148 if err != nil {149 t.Fatal(err)150 }151 if string(data) == "old" {152 t.Fatal("expected output file to be overwritten")153 }154 if !strings.Contains(string(data), pdfexport.ExportSchemaV1) {155 t.Fatal("expected jsonl export schema marker")156 }157}158159func TestRunNewExportOmitsPrintPayload(t *testing.T) {160 withExportFlagReset(t)161 flagExport = 1162163 cmd, out := newExportTestCmd(t, true)164 if err := runNewExport(cmd, []string{"sudoku", "easy"}); err != nil {165 t.Fatalf("expected sudoku export success, got error: %v", err)166 }167168 lines := strings.Split(strings.TrimSpace(out.String()), "\n")169 if len(lines) != 1 {170 t.Fatalf("jsonl lines = %d, want 1", len(lines))171 }172173 var record map[string]any174 if err := json.Unmarshal([]byte(lines[0]), &record); err != nil {175 t.Fatal(err)176 }177 if _, ok := record["print"]; ok {178 t.Fatal("did not expect print payload in export record")179 }180181 puzzle, ok := record["puzzle"].(map[string]any)182 if !ok {183 t.Fatalf("expected puzzle object, got %T", record["puzzle"])184 }185 if _, ok := puzzle["snippet"]; ok {186 t.Fatal("did not expect markdown snippet in export record")187 }188}189190func withExportFlagReset(t *testing.T) {191 t.Helper()192193 prevSetSeed := flagSetSeed194 prevWithSeed := flagWithSeed195 prevExport := flagExport196 prevOutput := flagOutput197198 flagSetSeed = ""199 flagWithSeed = ""200 flagExport = 1201 flagOutput = ""202203 t.Cleanup(func() {204 flagSetSeed = prevSetSeed205 flagWithSeed = prevWithSeed206 flagExport = prevExport207 flagOutput = prevOutput208 })209}210211func newExportTestCmd(t *testing.T, exportChanged bool) (*cobra.Command, *bytes.Buffer) {212 t.Helper()213214 cmd := &cobra.Command{}215 var out bytes.Buffer216 cmd.SetOut(&out)217 cmd.Flags().Int("export", 1, "")218 if exportChanged {219 if err := cmd.Flags().Set("export", strconv.Itoa(flagExport)); err != nil {220 t.Fatal(err)221 }222 }223 return cmd, &out224}