Documentation
¶
Overview ¶
Package testsupport provides shared test helpers used across multiple packages. It avoids duplicating mock implementations in each test file.
Package testsupport — scanner-alignment helpers.
The store package consolidates SQL column lists and Scan-argument lists in tableScanner-like structs (each scanner pairs a `columns` string with a `scan` or `scanInto` function). This file provides reusable helpers that detect drift between those two halves at test time.
The bug class targeted: a column added to the SELECT list without a matching pointer in the Scan call (or vice versa) panics at runtime when the query is executed. The helpers below catch this in `go test` instead.
Borrows sqlc's safety property (compile-time scan-order validation) without taking the codegen tool. Coverage is partial: the test catches add/remove drift definitively and reorder drift when the test author supplies an expected-field list.
Index ¶
- func FieldNameAt(sample, ptr any) string
- func SplitColumns(s string) []string
- type AlignmentResult
- type NopConfig
- func (n *NopConfig) Adaptive() api.AdaptiveConfig
- func (n *NopConfig) MinScoreForTarget(_ *api.SubtitleTarget, _ api.MediaType) int
- func (n *NopConfig) PostProcessConfig() api.PostProcessConfig
- func (n *NopConfig) ProviderConfigs() map[api.ProviderID]api.ProviderCfg
- func (n *NopConfig) ProviderPriority(_ api.ProviderID) int
- func (n *NopConfig) ProvidersForTarget(_ *api.SubtitleTarget, all []api.ProviderID) []api.ProviderID
- func (n *NopConfig) Scores() api.Scores
- func (n *NopConfig) Search() api.SearchConfig
- func (n *NopConfig) SyncConfig() api.SyncConfig
- type NopStore
- func (*NopStore) BackedOffProviders(context.Context, api.MediaType, string, string, int) ([]api.ProviderID, error)
- func (*NopStore) CleanupDrift(context.Context, api.ConfigDrift) error
- func (*NopStore) ClearManualLock(context.Context, api.MediaType, string, string) error
- func (*NopStore) Close(context.Context) error
- func (*NopStore) CurrentScore(context.Context, api.MediaType, string, string) (score int, at time.Time, found bool, _ error)
- func (*NopStore) DeleteStateByPaths(context.Context, []string) (api.CleanupResult, error)
- func (*NopStore) DeleteSubtitleFile(context.Context, api.MediaType, string, string, api.Variant, ...) error
- func (*NopStore) DownloadedRefs(context.Context, api.MediaType, string, string) ([]api.DownloadedRef, error)
- func (*NopStore) GetBackoffByPrefix(context.Context, api.MediaType, string) ([]api.BackoffEntry, error)
- func (*NopStore) GetBackoffItems(context.Context) ([]api.BackoffEntry, error)
- func (*NopStore) GetManualLocks(context.Context) ([]api.ManualLockEntry, error)
- func (*NopStore) GetPollTimestamp(context.Context, api.PollKey) (time.Time, error)
- func (*NopStore) GetScanStates(context.Context, api.MediaType, string) ([]api.ScanStateRow, error)
- func (*NopStore) GetState(context.Context, *api.StateQuery) ([]api.StateEntry, error)
- func (*NopStore) GetSubtitleFiles(context.Context, api.MediaType, string) ([]api.SubtitleFileRow, error)
- func (*NopStore) GetSyncOffset(context.Context, string) (int64, error)
- func (*NopStore) HistoryMediaIDs(context.Context, api.MediaType, string) ([]string, error)
- func (*NopStore) IsManuallyLocked(context.Context, api.MediaType, string, string) (bool, error)
- func (*NopStore) LastScanTime(context.Context) (string, error)
- func (*NopStore) ManualDownloadCount(context.Context, api.MediaType, string, string) (int, error)
- func (*NopStore) ManualSubtitlePaths(context.Context, api.MediaType, string, string) ([]string, error)
- func (*NopStore) NextManualNumber(context.Context, api.MediaType, string, string) int
- func (*NopStore) RecentlyScanned(context.Context, time.Time) (map[string]bool, error)
- func (*NopStore) ReconcileState(context.Context) (api.ReconcileResult, error)
- func (*NopStore) RecordNoResult(context.Context, api.MediaType, string, string, api.ProviderID, ...) error
- func (*NopStore) RecordScanState(context.Context, *api.ScanRecord) error
- func (*NopStore) RecordSubtitleFiles(context.Context, api.MediaType, string, []api.SubtitleFile) (bool, error)
- func (*NopStore) SaveDownload(context.Context, *api.DownloadRecord) error
- func (*NopStore) SetPollTimestamp(context.Context, api.PollKey, time.Time) error
- func (*NopStore) SetSyncOffset(context.Context, string, int64) error
- func (*NopStore) Stats(context.Context) (downloads, backoffs int, _ error)
- func (*NopStore) TotalSubtitleFiles(context.Context) (int, error)
- func (*NopStore) UpsertSubtitleFile(context.Context, api.MediaType, string, *api.SubtitleFile) error
- type ScanRecorder
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FieldNameAt ¶
FieldNameAt reports the name of the struct field within `sample` that holds the address `ptr`. Returns "" if `ptr` is outside the memory range of `sample` (a local variable passed to Scan instead of a struct field, e.g. `var manual int; row.Scan(..., &manual, ...)`).
Both arguments must be pointers; sample must be a pointer to a struct. The check is offset-based: each pointer is compared against the addresses of every exported and unexported field. Embedded struct fields are not unwrapped; pass the embedded type directly if needed.
func SplitColumns ¶
SplitColumns parses a `tableScanner.columns` string into a slice of trimmed column names. Handles multi-line column lists with tabs, newlines, and surrounding whitespace.
Example:
SplitColumns("id, user_id,\n\ttoken_hash, created_at")
-> ["id", "user_id", "token_hash", "created_at"]
Types ¶
type AlignmentResult ¶
type AlignmentResult struct {
// Columns is the parsed column list from the scanner's columns string.
Columns []string
// ScanArgs is the slice of pointer args captured by ScanRecorder.
ScanArgs []any
// Fields contains the resolved field name for each ScanArg, or ""
// for args that point outside the sample struct (local variables).
Fields []string
}
AlignmentResult captures the outcome of an Aligned check. The test author decides which assertions to run against it: count match alone catches add/remove drift; comparing Fields against an expected slice catches reorder drift.
type NopConfig ¶
type NopConfig struct {
ProviderCfgs map[api.ProviderID]api.ProviderCfg
SearchConfig api.SearchConfig
AdaptiveCfg api.AdaptiveConfig
Sync api.SyncConfig
PostProcess api.PostProcessConfig
MinScore int
ProviderPrio int
}
NopConfig is a zero-value implementation of search.SearchCfg for tests. Embed it in test-specific mocks and override only the methods you need. Note: the compile-time assertion against search.SearchCfg lives in search/nopconfig_assert_test.go to avoid testsupport→search→testsupport import cycles (search test code imports testsupport).
func (*NopConfig) Adaptive ¶
func (n *NopConfig) Adaptive() api.AdaptiveConfig
func (*NopConfig) MinScoreForTarget ¶
func (*NopConfig) PostProcessConfig ¶
func (n *NopConfig) PostProcessConfig() api.PostProcessConfig
func (*NopConfig) ProviderConfigs ¶
func (n *NopConfig) ProviderConfigs() map[api.ProviderID]api.ProviderCfg
func (*NopConfig) ProviderPriority ¶
func (n *NopConfig) ProviderPriority(_ api.ProviderID) int
func (*NopConfig) ProvidersForTarget ¶
func (n *NopConfig) ProvidersForTarget(_ *api.SubtitleTarget, all []api.ProviderID) []api.ProviderID
func (*NopConfig) Search ¶
func (n *NopConfig) Search() api.SearchConfig
func (*NopConfig) SyncConfig ¶
func (n *NopConfig) SyncConfig() api.SyncConfig
type NopStore ¶
type NopStore struct {
// contains filtered or unexported fields
} //nolint:unused // embedded for compile-time interface safety
NopStore implements api.Store with all methods returning zero/nil values. Embed it in test-specific mock structs and override only the methods relevant to each test case.
NopStore embeds panicStore (which panics on every method) and overrides all methods with no-op returns. When a new method is added to api.Store, only panicStore needs updating (the compiler tells you); NopStore then inherits the panic until the no-op override is added here.
func (*NopStore) BackedOffProviders ¶
func (*NopStore) CleanupDrift ¶
func (*NopStore) ClearManualLock ¶
func (*NopStore) CurrentScore ¶
func (*NopStore) DeleteStateByPaths ¶
func (*NopStore) DeleteSubtitleFile ¶
func (*NopStore) DownloadedRefs ¶
func (*NopStore) GetBackoffByPrefix ¶
func (*NopStore) GetBackoffItems ¶
func (*NopStore) GetManualLocks ¶
func (*NopStore) GetPollTimestamp ¶
func (*NopStore) GetScanStates ¶
func (*NopStore) GetState ¶
func (*NopStore) GetState(context.Context, *api.StateQuery) ([]api.StateEntry, error)
func (*NopStore) GetSubtitleFiles ¶
func (*NopStore) GetSyncOffset ¶
func (*NopStore) HistoryMediaIDs ¶
func (*NopStore) IsManuallyLocked ¶
func (*NopStore) ManualDownloadCount ¶
func (*NopStore) ManualSubtitlePaths ¶
func (*NopStore) NextManualNumber ¶
func (*NopStore) RecentlyScanned ¶
func (*NopStore) ReconcileState ¶
func (*NopStore) RecordNoResult ¶
func (*NopStore) RecordScanState ¶
func (*NopStore) RecordSubtitleFiles ¶
func (*NopStore) SaveDownload ¶
func (*NopStore) SetPollTimestamp ¶
func (*NopStore) SetSyncOffset ¶
func (*NopStore) TotalSubtitleFiles ¶
type ScanRecorder ¶
type ScanRecorder struct {
Args []any
}
ScanRecorder satisfies the `interface{ Scan(...any) error }` shape used by row scanners and captures the pointer arguments passed to Scan in the order they were passed.
func (*ScanRecorder) Scan ¶
func (r *ScanRecorder) Scan(args ...any) error
Scan records args and returns nil. Pass &ScanRecorder{} as the row to any scanInto / scan function to capture its argument order.