Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ef410b140 |
+3
-19
@@ -47,9 +47,7 @@ FROM golang:1.26.4-alpine@sha256:3ad57304ad93bbec8548a0437ad9e06a455660655d9af01
|
|||||||
COPY --from=lint /src/go.sum /dev/null
|
COPY --from=lint /src/go.sum /dev/null
|
||||||
COPY --from=test /src/go.sum /dev/null
|
COPY --from=test /src/go.sum /dev/null
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
ARG VERSION=dev
|
||||||
# A tar-stream context keeps the sender's file owners, which git refuses.
|
|
||||||
RUN git config --system --add safe.directory /src
|
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
@@ -60,22 +58,8 @@ RUN go mod download
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build (pure Go, no CGO required since we use modernc.org/sqlite).
|
# Build (pure Go, no CGO required since we use modernc.org/sqlite)
|
||||||
# The VERSION build arg when one is given, otherwise
|
RUN CGO_ENABLED=0 go build -o /bsdaily ./cmd/bsdaily
|
||||||
# `git describe --tags --always` on the .git in the build context. With
|
|
||||||
# .git present, a version that is still empty, dev or unknown fails the
|
|
||||||
# build: git is missing or could not read the checkout.
|
|
||||||
ARG VERSION
|
|
||||||
RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \
|
|
||||||
if [ -e .git ]; then \
|
|
||||||
case "$VERSION" in ""|dev|unknown) \
|
|
||||||
echo "version is '$VERSION' although .git is present" >&2; \
|
|
||||||
exit 1 ;; \
|
|
||||||
esac; \
|
|
||||||
fi; \
|
|
||||||
CGO_ENABLED=0 go build -trimpath \
|
|
||||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
|
||||||
-o /bsdaily ./cmd/bsdaily
|
|
||||||
|
|
||||||
# Runtime stage
|
# Runtime stage
|
||||||
# alpine:3.21, 2026-06-28
|
# alpine:3.21, 2026-06-28
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
.PHONY: all bootstrap setup check test lint fmt fmt-check build clean deps test-coverage test-integration install release release-snapshot docker hooks
|
.PHONY: all bootstrap setup check test lint fmt fmt-check build clean deps test-coverage test-integration install release release-snapshot docker hooks
|
||||||
|
|
||||||
# Stamped into the binary: the same `git describe` a plain `docker build .`
|
# Version number
|
||||||
# runs, or dev when it prints nothing (outside a git checkout, or where git is
|
VERSION := 0.1.0-dev
|
||||||
# missing). ?= so that a VERSION already in the environment takes precedence.
|
|
||||||
VERSION ?= $(or $(shell git describe --tags --always 2>/dev/null),dev)
|
|
||||||
|
|
||||||
# Default target
|
# Default target
|
||||||
all: bsdaily
|
all: bsdaily
|
||||||
@@ -38,7 +36,7 @@ lint:
|
|||||||
|
|
||||||
# Build binary (pure Go; no CGO required since we use modernc.org/sqlite).
|
# Build binary (pure Go; no CGO required since we use modernc.org/sqlite).
|
||||||
bsdaily: internal/*/*.go cmd/bsdaily/*.go
|
bsdaily: internal/*/*.go cmd/bsdaily/*.go
|
||||||
CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -o $@ ./cmd/bsdaily
|
CGO_ENABLED=0 go build -o $@ ./cmd/bsdaily
|
||||||
|
|
||||||
# Clean build artifacts.
|
# Clean build artifacts.
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -156,11 +156,6 @@ included to do exactly this and to rebuild the aggregate statistics
|
|||||||
|
|
||||||
# How It Works
|
# How It Works
|
||||||
|
|
||||||
`cmd/bsdaily/main.go` only passes the version to `internal/cli` and exits with
|
|
||||||
the status it returns. `internal/cli` holds the command line: the flags, the
|
|
||||||
rules for combining them and the parsing of the dates they name.
|
|
||||||
`internal/bsdaily` does the extraction.
|
|
||||||
|
|
||||||
A single run proceeds as follows:
|
A single run proceeds as follows:
|
||||||
|
|
||||||
1. **Find the snapshot.** Scan `SnapshotBase` for directories matching
|
1. **Find the snapshot.** Scan `SnapshotBase` for directories matching
|
||||||
@@ -194,7 +189,6 @@ A single run proceeds as follows:
|
|||||||
bsdaily # extract the snapshot date minus one day
|
bsdaily # extract the snapshot date minus one day
|
||||||
bsdaily --date 2026-06-27 # extract a single specific day
|
bsdaily --date 2026-06-27 # extract a single specific day
|
||||||
bsdaily --from 2026-06-01 --to 2026-06-27 # extract an inclusive range
|
bsdaily --from 2026-06-01 --to 2026-06-27 # extract an inclusive range
|
||||||
bsdaily --version # print the version and exit
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
@@ -203,24 +197,9 @@ Flags:
|
|||||||
`--from`/`--to`.
|
`--from`/`--to`.
|
||||||
- `--from YYYY-MM-DD` — start of an inclusive range (requires `--to`).
|
- `--from YYYY-MM-DD` — start of an inclusive range (requires `--to`).
|
||||||
- `--to YYYY-MM-DD` — end of an inclusive range (requires `--from`).
|
- `--to YYYY-MM-DD` — end of an inclusive range (requires `--from`).
|
||||||
- `-v`, `--version` — print the version and exit.
|
|
||||||
|
|
||||||
With no flags, the tool extracts the day before the latest snapshot. All
|
With no flags, the tool extracts the day before the latest snapshot. All
|
||||||
progress is logged as structured `slog` text to stderr; the first line of every
|
progress is logged as structured `slog` text to stderr.
|
||||||
run carries the version.
|
|
||||||
|
|
||||||
The version is set at link time and depends on how the binary was built:
|
|
||||||
|
|
||||||
- `docker build .` takes it from the `VERSION` build argument when one is given,
|
|
||||||
otherwise from `git describe --tags --always` on the `.git` in the build
|
|
||||||
context. The build fails if `.git` is there and the version still comes out
|
|
||||||
empty, `dev` or `unknown`. With neither `.git` nor `VERSION`, the binary
|
|
||||||
reports `dev`.
|
|
||||||
- `script/docker`, `script/cibuild` and `make docker` pass the host's
|
|
||||||
`git describe --tags --always --dirty` as `VERSION`, so on a modified tree the
|
|
||||||
version ends in `-dirty`. When that prints nothing, they pass `unknown`.
|
|
||||||
- `make` stamps the host's `git describe --tags --always`, without `-dirty`, or
|
|
||||||
`dev` when that prints nothing.
|
|
||||||
|
|
||||||
## Merging dumps back into a database
|
## Merging dumps back into a database
|
||||||
|
|
||||||
|
|||||||
@@ -14,19 +14,11 @@ pre-1.0
|
|||||||
|
|
||||||
# Next Step
|
# Next Step
|
||||||
|
|
||||||
Expand the `internal/bsdaily` tests beyond the compilation smoke test: unit
|
Expand tests beyond the compilation smoke test: unit tests for the extraction,
|
||||||
tests for the extraction, verification, and atomic-publish paths.
|
verification, and atomic-publish paths.
|
||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-10-06: Moved the command line (flags, the rules for combining them, date
|
|
||||||
parsing) from `cmd/bsdaily` into `internal/cli`, with unit tests for the flag
|
|
||||||
rules and the dates; `cmd/bsdaily/main.go` is now a single call into it
|
|
||||||
(https://git.eeqj.de/sneak/bsdaily/issues/8).
|
|
||||||
- 2026-10-06: A plain `docker build .` and a host `make` build stamp the git tag
|
|
||||||
or short commit into the binary, which `bsdaily` logs on the first line of
|
|
||||||
every run and prints with `--version`
|
|
||||||
(https://git.eeqj.de/sneak/bsdaily/issues/4).
|
|
||||||
- 2026-10-06: Added the canonical `.golangci.yml`, moved the lint phase to
|
- 2026-10-06: Added the canonical `.golangci.yml`, moved the lint phase to
|
||||||
golangci-lint v2.14.0, and fixed the code to pass it
|
golangci-lint v2.14.0, and fixed the code to pass it
|
||||||
(https://git.eeqj.de/sneak/bsdaily/issues/6).
|
(https://git.eeqj.de/sneak/bsdaily/issues/6).
|
||||||
|
|||||||
+112
-8
@@ -3,18 +3,122 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/bsdaily/internal/cli"
|
"git.eeqj.de/sneak/bsdaily/internal/bsdaily"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version is the git tag or short commit, set at link time with
|
var (
|
||||||
// -X main.Version=... by the Dockerfile and the Makefile. A build that sets
|
errDateExclusive = errors.New("--date and --from/--to are mutually exclusive")
|
||||||
// nothing, or sets it empty, reports dev.
|
errFromRequiresTo = errors.New("--from requires --to")
|
||||||
//
|
errToRequiresFrom = errors.New("--to requires --from")
|
||||||
//nolint:gochecknoglobals // -X can only set a package-level variable
|
errFromAfterTo = errors.New("is after --to")
|
||||||
var Version string
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
os.Exit(cli.Main(Version))
|
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
|
Level: slog.LevelInfo,
|
||||||
|
}))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
var dateFlag, fromFlag, toFlag string
|
||||||
|
|
||||||
|
rootCmd := &cobra.Command{
|
||||||
|
Use: "bsdaily",
|
||||||
|
Short: "Extract a single day's data from the latest daily snapshot",
|
||||||
|
SilenceUsage: true,
|
||||||
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
|
targetDates, err := parseTargetDates(dateFlag, fromFlag, toFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bsdaily.Run(targetDates)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("completed successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.Flags().StringVarP(&dateFlag, "date", "d", "",
|
||||||
|
"target date to extract (YYYY-MM-DD); "+
|
||||||
|
"defaults to snapshot date minus one day")
|
||||||
|
rootCmd.Flags().StringVar(&fromFlag, "from", "",
|
||||||
|
"start of date range to extract (YYYY-MM-DD, inclusive); use with --to")
|
||||||
|
rootCmd.Flags().StringVar(&toFlag, "to", "",
|
||||||
|
"end of date range to extract (YYYY-MM-DD, inclusive); use with --from")
|
||||||
|
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTargetDates turns the --date, --from and --to flags into the days
|
||||||
|
// to extract. It returns nil when none of them is set, which Run takes
|
||||||
|
// to mean the snapshot date minus one day.
|
||||||
|
func parseTargetDates(dateFlag, fromFlag, toFlag string) ([]time.Time, error) {
|
||||||
|
hasDate := dateFlag != ""
|
||||||
|
hasFrom := fromFlag != ""
|
||||||
|
hasTo := toFlag != ""
|
||||||
|
|
||||||
|
// Validate mutual exclusivity
|
||||||
|
if hasDate && (hasFrom || hasTo) {
|
||||||
|
return nil, errDateExclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasFrom != hasTo {
|
||||||
|
if hasFrom {
|
||||||
|
return nil, errFromRequiresTo
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errToRequiresFrom
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasDate {
|
||||||
|
t, err := time.Parse("2006-01-02", dateFlag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"invalid --date %q (expected YYYY-MM-DD): %w", dateFlag, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []time.Time{t}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasFrom {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
from, err := time.Parse("2006-01-02", fromFlag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"invalid --from %q (expected YYYY-MM-DD): %w", fromFlag, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
to, err := time.Parse("2006-01-02", toFlag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"invalid --to %q (expected YYYY-MM-DD): %w", toFlag, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if from.After(to) {
|
||||||
|
return nil, fmt.Errorf("--from %s %w %s", fromFlag, errFromAfterTo, toFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetDates []time.Time
|
||||||
|
|
||||||
|
for d := from; !d.After(to); d = d.AddDate(0, 0, 1) {
|
||||||
|
targetDates = append(targetDates, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetDates, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// Package cli is the bsdaily command line: the command, its flags, the
|
|
||||||
// rules for combining them and the dates they name. The extraction itself
|
|
||||||
// is in package bsdaily.
|
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.eeqj.de/sneak/bsdaily/internal/bsdaily"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Main runs the bsdaily command on the program's command-line arguments
|
|
||||||
// and returns the status for the process to exit with. version is the
|
|
||||||
// build's version; an empty one is reported as dev.
|
|
||||||
func Main(version string) int {
|
|
||||||
if version == "" {
|
|
||||||
version = "dev"
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
|
||||||
Level: slog.LevelInfo,
|
|
||||||
}))
|
|
||||||
slog.SetDefault(logger)
|
|
||||||
|
|
||||||
var dateFlag, fromFlag, toFlag string
|
|
||||||
|
|
||||||
rootCmd := &cobra.Command{
|
|
||||||
Use: "bsdaily",
|
|
||||||
Short: "Extract a single day's data from the latest daily snapshot",
|
|
||||||
Version: version,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(_ *cobra.Command, _ []string) error {
|
|
||||||
slog.Info("starting", "version", version)
|
|
||||||
|
|
||||||
targetDates, err := ParseTargetDates(dateFlag, fromFlag, toFlag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bsdaily.Run(targetDates)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("completed successfully")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
rootCmd.Flags().StringVarP(&dateFlag, "date", "d", "",
|
|
||||||
"target date to extract (YYYY-MM-DD); "+
|
|
||||||
"defaults to snapshot date minus one day")
|
|
||||||
rootCmd.Flags().StringVar(&fromFlag, "from", "",
|
|
||||||
"start of date range to extract (YYYY-MM-DD, inclusive); use with --to")
|
|
||||||
rootCmd.Flags().StringVar(&toFlag, "to", "",
|
|
||||||
"end of date range to extract (YYYY-MM-DD, inclusive); use with --from")
|
|
||||||
|
|
||||||
err := rootCmd.Execute()
|
|
||||||
if err != nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errDateExclusive = errors.New("--date and --from/--to are mutually exclusive")
|
|
||||||
errFromRequiresTo = errors.New("--from requires --to")
|
|
||||||
errToRequiresFrom = errors.New("--to requires --from")
|
|
||||||
errFromAfterTo = errors.New("is after --to")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseTargetDates turns the --date, --from and --to flags into the days
|
|
||||||
// to extract. It returns nil when none of them is set, which bsdaily.Run
|
|
||||||
// takes to mean the snapshot date minus one day.
|
|
||||||
func ParseTargetDates(dateFlag, fromFlag, toFlag string) ([]time.Time, error) {
|
|
||||||
hasDate := dateFlag != ""
|
|
||||||
hasFrom := fromFlag != ""
|
|
||||||
hasTo := toFlag != ""
|
|
||||||
|
|
||||||
// Validate mutual exclusivity
|
|
||||||
if hasDate && (hasFrom || hasTo) {
|
|
||||||
return nil, errDateExclusive
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasFrom != hasTo {
|
|
||||||
if hasFrom {
|
|
||||||
return nil, errFromRequiresTo
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errToRequiresFrom
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasDate {
|
|
||||||
t, err := time.Parse("2006-01-02", dateFlag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"invalid --date %q (expected YYYY-MM-DD): %w", dateFlag, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []time.Time{t}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasFrom {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
from, err := time.Parse("2006-01-02", fromFlag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"invalid --from %q (expected YYYY-MM-DD): %w", fromFlag, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
to, err := time.Parse("2006-01-02", toFlag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"invalid --to %q (expected YYYY-MM-DD): %w", toFlag, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if from.After(to) {
|
|
||||||
return nil, fmt.Errorf("--from %s %w %s", fromFlag, errFromAfterTo, toFlag)
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetDates []time.Time
|
|
||||||
|
|
||||||
for d := from; !d.After(to); d = d.AddDate(0, 0, 1) {
|
|
||||||
targetDates = append(targetDates, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetDates, nil
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package cli_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.eeqj.de/sneak/bsdaily/internal/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseTargetDates(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const oneDay = "2026-05-14"
|
|
||||||
|
|
||||||
// want lists the expected days as YYYY-MM-DD.
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
date string
|
|
||||||
from string
|
|
||||||
to string
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{name: "no flags"},
|
|
||||||
{name: "single date", date: "2026-06-27", want: []string{"2026-06-27"}},
|
|
||||||
{
|
|
||||||
name: "range across a month end", from: "2026-06-29", to: "2026-07-02",
|
|
||||||
want: []string{"2026-06-29", "2026-06-30", "2026-07-01", "2026-07-02"},
|
|
||||||
},
|
|
||||||
{name: "range of one day", from: oneDay, to: oneDay, want: []string{oneDay}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
got, err := cli.ParseTargetDates(tt.date, tt.from, tt.to)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
days := make([]string, 0, len(got))
|
|
||||||
for _, day := range got {
|
|
||||||
days = append(days, day.Format("2006-01-02"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !slices.Equal(days, tt.want) {
|
|
||||||
t.Errorf("days = %v, want %v", days, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseTargetDatesErrors(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const dateExclusive = "--date and --from/--to are mutually exclusive"
|
|
||||||
|
|
||||||
// wantErr is the whole error message. With startsWith set it is only how
|
|
||||||
// the message starts: for a malformed date, the date parser's own
|
|
||||||
// explanation follows it.
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
date string
|
|
||||||
from string
|
|
||||||
to string
|
|
||||||
wantErr string
|
|
||||||
startsWith bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "from after to", from: "2026-03-02", to: "2026-03-01",
|
|
||||||
wantErr: "--from 2026-03-02 is after --to 2026-03-01",
|
|
||||||
},
|
|
||||||
{name: "from without to", from: "2026-04-01", wantErr: "--from requires --to"},
|
|
||||||
{name: "to without from", to: "2026-04-02", wantErr: "--to requires --from"},
|
|
||||||
{
|
|
||||||
name: "date with from", date: "2026-01-05", from: "2026-01-06",
|
|
||||||
wantErr: dateExclusive,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "date with to", date: "2026-01-13", to: "2026-01-14",
|
|
||||||
wantErr: dateExclusive,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "date with from and to", date: "2026-01-07",
|
|
||||||
from: "2026-01-08", to: "2026-01-09",
|
|
||||||
wantErr: dateExclusive,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "malformed date", date: "27.06.2026",
|
|
||||||
wantErr: `invalid --date "27.06.2026" (expected YYYY-MM-DD): `,
|
|
||||||
startsWith: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "date that does not exist", date: "2026-02-30",
|
|
||||||
wantErr: `invalid --date "2026-02-30" (expected YYYY-MM-DD): `,
|
|
||||||
startsWith: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "malformed from", from: "2026-1-10", to: "2026-01-11",
|
|
||||||
wantErr: `invalid --from "2026-1-10" (expected YYYY-MM-DD): `,
|
|
||||||
startsWith: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "malformed to", from: "2026-01-12", to: "tomorrow",
|
|
||||||
wantErr: `invalid --to "tomorrow" (expected YYYY-MM-DD): `,
|
|
||||||
startsWith: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
_, err := cli.ParseTargetDates(tt.date, tt.from, tt.to)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
t.Errorf("no error, want %q", tt.wantErr)
|
|
||||||
case tt.startsWith && !strings.HasPrefix(err.Error(), tt.wantErr):
|
|
||||||
t.Errorf("error = %q, want one starting %q", err, tt.wantErr)
|
|
||||||
case !tt.startsWith && err.Error() != tt.wantErr:
|
|
||||||
t.Errorf("error = %q, want %q", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user