package cmd import ( "context" "encoding/json" "fmt" "os" "path/filepath" "time" "github.com/spf13/cobra" "github.com/yourorg/devour/internal/scraper" ) var ( verifyFormat string verifyTimeout int ) var verifyCmd = &cobra.Command{ Use: "verify", Short: "Run Devour verification suites", Long: `Run deterministic and live verification suites for Devour commands and scrapers.`, } var verifySmokeCmd = &cobra.Command{ Use: "smoke", Short: "Run live docs scraping smoke checks", Long: `Run an opt-in live network smoke suite and persist a machine-readable report under devour_data/verify/.`, RunE: runVerifySmoke, } func init() { verifyCmd.AddCommand(verifySmokeCmd) verifySmokeCmd.Flags().StringVar(&verifyFormat, "format", "text", "output format (text, json)") verifySmokeCmd.Flags().IntVar(&verifyTimeout, "timeout", 90, "timeout per smoke case in seconds") } type verifyCase struct { Name string `json:"name"` Type scraper.SourceType `json:"type"` URL string `json:"url"` Passed bool `json:"passed"` Docs int `json:"docs"` Error string `json:"error,omitempty"` TookMs int64 `json:"took_ms"` } type verifyReport struct { CreatedAt time.Time `json:"created_at"` Duration string `json:"duration"` Passed int `json:"passed"` Failed int `json:"failed"` Cases []verifyCase `json:"cases"` } func runVerifySmoke(cmd *cobra.Command, args []string) error { cfg, err := loadAppConfig() if err != nil { return err } if verifyTimeout <= 0 { verifyTimeout = 90 } cases := []verifyCase{ {Name: "Go net/http", Type: scraper.SourceTypeGoDocs, URL: "https://pkg.go.dev/net/http"}, {Name: "Python asyncio", Type: scraper.SourceTypePythonDocs, URL: "https://docs.python.org/3/library/asyncio.html"}, {Name: "React reference", Type: scraper.SourceTypeReactDocs, URL: "https://react.dev/reference/react"}, {Name: "TypeScript handbook", Type: scraper.SourceTypeTSDocs, URL: "https://www.typescriptlang.org/docs/handbook/2/basic-types.html"}, {Name: "Next.js docs", Type: scraper.SourceTypeWeb, URL: "https://nextjs.org/docs"}, {Name: "Svelte docs", Type: scraper.SourceTypeWeb, URL: "https://svelte.dev/docs/kit"}, {Name: "Angular guide", Type: scraper.SourceTypeWeb, URL: "https://angular.dev/guide/http"}, {Name: "Remix docs", Type: scraper.SourceTypeWeb, URL: "https://v2.remix.run/docs"}, {Name: "Solid docs repo", Type: scraper.SourceTypeGitHub, URL: "https://github.com/solidjs/solid-docs"}, {Name: "Express guide", Type: scraper.SourceTypeWeb, URL: "https://expressjs.com/en/guide/routing.html"}, } startAll := time.Now() passed := 0 failed := 0 for i := range cases { c := &cases[i] caseStart := time.Now() s := scraper.NewScraper(c.Type, toScraperConfig(cfg, 4)) if s == nil { c.Error = "scraper not available" c.Passed = false failed++ continue } ctx, cancel := context.WithTimeout(context.Background(), time.Duration(verifyTimeout)*time.Second) docs, err := s.Scrape(ctx, &scraper.Source{Name: c.Name, Type: c.Type, URL: c.URL}) cancel() c.TookMs = time.Since(caseStart).Milliseconds() if err != nil { c.Error = err.Error() c.Passed = false failed++ continue } c.Docs = len(docs) if len(docs) == 0 { c.Error = "0 documents" c.Passed = false failed++ continue } c.Passed = true passed++ } report := verifyReport{ CreatedAt: time.Now(), Duration: time.Since(startAll).String(), Passed: passed, Failed: failed, Cases: cases, } rootDataDir := filepath.Dir(cfg.Storage.DocsDir) verifyDir := filepath.Join(rootDataDir, "verify") if err := os.MkdirAll(verifyDir, 0o755); err != nil { return err } filename := fmt.Sprintf("smoke-%s.json", time.Now().Format("20060102-150405")) reportPath := filepath.Join(verifyDir, filename) b, err := json.MarshalIndent(report, "", " ") if err != nil { return err } if err := os.WriteFile(reportPath, b, 0o644); err != nil { return err } switch verifyFormat { case "json": enc := json.NewEncoder(cmd.OutOrStdout()) enc.SetIndent("", " ") if err := enc.Encode(report); err != nil { return err } default: fmt.Fprintf(cmd.OutOrStdout(), "Smoke verification complete\n") fmt.Fprintf(cmd.OutOrStdout(), " Passed: %d\n", report.Passed) fmt.Fprintf(cmd.OutOrStdout(), " Failed: %d\n", report.Failed) fmt.Fprintf(cmd.OutOrStdout(), " Report: %s\n", reportPath) for _, c := range report.Cases { status := "PASS" if !c.Passed { status = "FAIL" } fmt.Fprintf(cmd.OutOrStdout(), " - [%s] %s (%d docs, %dms)", status, c.Name, c.Docs, c.TookMs) if c.Error != "" { fmt.Fprintf(cmd.OutOrStdout(), " error=%s", c.Error) } fmt.Fprintln(cmd.OutOrStdout()) } } if report.Failed > 0 { return fmt.Errorf("smoke verification completed with failures") } return nil }