package postgres import ( "reflect" "strings" "testing" fksinks "gitea.maximumdirect.net/ejr/feedkit/sinks" ) func TestWeatherPostgresSchemaShape(t *testing.T) { s := PostgresSchema() if s.MapEvent == nil { t.Fatalf("PostgresSchema().MapEvent is nil") } wantTables := map[string]bool{ tableObservations: true, tableObservationPresentWeather: true, tableForecasts: true, tableForecastPeriods: true, tableForecastDiscussions: true, tableForecastDiscussionKeyMessages: true, tableWeatherStoryRuns: true, tableWeatherStories: true, tableAlertRuns: true, tableAlerts: true, tableAlertReferences: true, tableOutlookRuns: true, tableOutlooks: true, tableOutlookDiscussions: true, } if len(s.Tables) != len(wantTables) { t.Fatalf("PostgresSchema().Tables len = %d, want %d", len(s.Tables), len(wantTables)) } seenIndexes := map[string]bool{} for _, tbl := range s.Tables { if !wantTables[tbl.Name] { t.Fatalf("unexpected table %q in schema", tbl.Name) } if tbl.PruneColumn == "" { t.Fatalf("table %q missing prune column", tbl.Name) } for _, idx := range tbl.Indexes { if seenIndexes[idx.Name] { t.Fatalf("duplicate index name %q", idx.Name) } seenIndexes[idx.Name] = true } } } func TestWeatherPostgresSchemaIncludesOutlookTables(t *testing.T) { runColumns := columnsForTable(t, tableOutlookRuns) for _, col := range []string{"event_id", "event_kind", "event_source", "event_schema", "event_emitted_at", "event_effective_at", "location_id", "location_name", "latitude", "longitude", "as_of", "issued_at", "outlook_count", "discussion_count"} { if !runColumns[col] { t.Fatalf("%s missing %s column", tableOutlookRuns, col) } } assertTablePrimaryKey(t, tableOutlookRuns, []string{"event_id"}) assertTableIndex(t, tableOutlookRuns, "idx_wf_outlook_run_location_as_of", []string{"location_id", "as_of"}) assertTableIndex(t, tableOutlookRuns, "idx_wf_outlook_run_as_of", []string{"as_of"}) outlookColumns := columnsForTable(t, tableOutlooks) for _, col := range []string{"run_event_id", "outlook_index", "as_of", "outlook_id", "provider", "product", "day", "outlook_type", "label", "label_text", "severity_rank", "valid_from", "valid_to", "issued_at", "expires_at", "forecaster", "source_url", "image_url", "contains_location", "geometry_json"} { if !outlookColumns[col] { t.Fatalf("%s missing %s column", tableOutlooks, col) } } for _, col := range []string{"headline", "summary", "discussion"} { if outlookColumns[col] { t.Fatalf("%s still includes legacy %s column", tableOutlooks, col) } } assertTablePrimaryKey(t, tableOutlooks, []string{"run_event_id", "outlook_index"}) assertTableIndex(t, tableOutlooks, "idx_wf_outlooks_contains_valid", []string{"contains_location", "valid_from", "valid_to"}) assertTableIndex(t, tableOutlooks, "idx_wf_outlooks_day_type_label", []string{"day", "outlook_type", "label"}) assertTableIndex(t, tableOutlooks, "idx_wf_outlooks_valid", []string{"valid_from", "valid_to"}) discussionColumns := columnsForTable(t, tableOutlookDiscussions) for _, col := range []string{"run_event_id", "discussion_index", "as_of", "day", "headline", "summary", "discussion", "updated_at"} { if !discussionColumns[col] { t.Fatalf("%s missing %s column", tableOutlookDiscussions, col) } } assertTablePrimaryKey(t, tableOutlookDiscussions, []string{"run_event_id", "discussion_index"}) assertTablePruneColumn(t, tableOutlookDiscussions, "as_of") assertTableIndex(t, tableOutlookDiscussions, "idx_wf_outlook_discussions_day_as_of", []string{"day", "as_of"}) assertTableUniqueIndex(t, tableOutlookDiscussions, "idx_wf_outlook_discussions_run_day", []string{"run_event_id", "day"}) } func TestWeatherPostgresSchemaIncludesAlertEndsColumn(t *testing.T) { alertColumns := columnsForTable(t, tableAlerts) for _, col := range []string{"run_event_id", "alert_index", "as_of", "alert_id", "onset", "ends", "expires"} { if !alertColumns[col] { t.Fatalf("%s missing %s column", tableAlerts, col) } } } func TestWeatherPostgresSchemaIncludesWeatherStoryColumns(t *testing.T) { runColumns := columnsForTable(t, tableWeatherStoryRuns) if !runColumns["as_of"] { t.Fatalf("%s missing as_of column", tableWeatherStoryRuns) } if !runColumns["story_count"] { t.Fatalf("%s missing story_count column", tableWeatherStoryRuns) } storyColumns := columnsForTable(t, tableWeatherStories) for _, col := range []string{"start_time", "end_time", "updated_at", "title", "description", "alt_text", "priority", "story_order", "download_url"} { if !storyColumns[col] { t.Fatalf("%s missing %s column", tableWeatherStories, col) } } } func TestWeatherPostgresSchemaParentTablesStartWithEnvelopeColumns(t *testing.T) { for _, table := range []string{ tableObservations, tableForecasts, tableForecastDiscussions, tableWeatherStoryRuns, tableAlertRuns, tableOutlookRuns, } { t.Run(table, func(t *testing.T) { columns := orderedColumnsForTable(t, table) want := parentEnvelopeColumns() if len(columns) < len(want) { t.Fatalf("%s has %d columns, want at least %d", table, len(columns), len(want)) } if !reflect.DeepEqual(columns[:len(want)], want) { t.Fatalf("%s envelope prefix = %#v, want %#v", table, columns[:len(want)], want) } }) } } func assertTablePrimaryKey(t *testing.T, table string, want []string) { t.Helper() tbl := tableByName(t, table) if strings.Join(tbl.PrimaryKey, ",") != strings.Join(want, ",") { t.Fatalf("%s primary key = %#v, want %#v", table, tbl.PrimaryKey, want) } } func assertTablePruneColumn(t *testing.T, table string, want string) { t.Helper() tbl := tableByName(t, table) if tbl.PruneColumn != want { t.Fatalf("%s prune column = %q, want %q", table, tbl.PruneColumn, want) } } func assertTableIndex(t *testing.T, table string, name string, want []string) { t.Helper() assertTableIndexWithUnique(t, table, name, want, false) } func assertTableUniqueIndex(t *testing.T, table string, name string, want []string) { t.Helper() assertTableIndexWithUnique(t, table, name, want, true) } func assertTableIndexWithUnique(t *testing.T, table string, name string, want []string, unique bool) { t.Helper() tbl := tableByName(t, table) for _, idx := range tbl.Indexes { if idx.Name == name { if strings.Join(idx.Columns, ",") != strings.Join(want, ",") { t.Fatalf("%s index %s columns = %#v, want %#v", table, name, idx.Columns, want) } if idx.Unique != unique { t.Fatalf("%s index %s unique = %v, want %v", table, name, idx.Unique, unique) } return } } t.Fatalf("%s missing index %s", table, name) } func tableByName(t *testing.T, table string) fksinks.PostgresTable { t.Helper() for _, tbl := range PostgresSchema().Tables { if tbl.Name == table { return tbl } } t.Fatalf("missing table %q", table) return fksinks.PostgresTable{} } func orderedColumnsForTable(t *testing.T, table string) []fksinks.PostgresColumn { t.Helper() schema := PostgresSchema() for _, tbl := range schema.Tables { if tbl.Name == table { return tbl.Columns } } t.Fatalf("missing table %q", table) return nil } func columnsForTable(t *testing.T, table string) map[string]bool { t.Helper() ordered := orderedColumnsForTable(t, table) cols := make(map[string]bool, len(ordered)) for _, col := range ordered { cols[col.Name] = true } return cols }