Files
zenfeed/pkg/scrape/manager_test.go
glidea 8b33df8a05 init
2025-04-19 15:50:26 +08:00

561 lines
17 KiB
Go

// Copyright (C) 2025 wangyusong
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package scrape
// import (
// "testing"
// "time"
// . "github.com/onsi/gomega"
// )
// func TestValidate(t *testing.T) {
// RegisterTestingT(t)
// tests := []struct {
// scenario string
// given string
// when string
// then string
// config *Config
// want *Config
// wantErr string
// }{
// {
// scenario: "Valid Configuration",
// given: "a valid configuration with RSS sources",
// when: "creating a new manager",
// then: "should create manager successfully",
// config: &Config{
// ScrapeInterval: time.Second,
// RSSs: []RSS{
// {
// Name: "test",
// URL: "http://example.com",
// ScrapeInterval: time.Second,
// Labels: map[string]string{},
// },
// },
// },
// },
// {
// scenario: "Invalid Global Interval",
// given: "a configuration with invalid global scrape interval",
// when: "creating a new manager",
// then: "should return interval validation error",
// config: &Config{
// ScrapeInterval: time.Millisecond,
// },
// wantErr: "scrape interval must be at least 1 second",
// },
// {
// scenario: "Invalid RSS Config",
// given: "a configuration with invalid RSS config",
// when: "creating a new manager",
// then: "should return RSS config validation error",
// config: &Config{
// ScrapeInterval: time.Second,
// RSSs: []RSS{
// {
// Name: "",
// URL: "",
// ScrapeInterval: time.Second,
// Labels: map[string]string{},
// },
// },
// },
// wantErr: "invalid RSS config",
// },
// {
// scenario: "Default Global Interval",
// given: "a configuration with zero global interval",
// when: "validating and adjusting the config",
// then: "should set default global interval",
// config: &Config{
// ScrapeInterval: 0,
// RSSs: []RSS{
// {
// Name: "test",
// URL: "http://example.com",
// ScrapeInterval: time.Second,
// Labels: map[string]string{},
// },
// },
// },
// want: &Config{
// ScrapeInterval: time.Hour, // default value
// RSSs: []RSS{
// {
// Name: "test",
// URL: "http://example.com",
// ScrapeInterval: time.Hour, // inherited from global
// Labels: map[string]string{},
// },
// },
// },
// },
// {
// scenario: "Default RSS Interval",
// given: "a configuration with zero RSS interval",
// when: "validating and adjusting the config",
// then: "should inherit global interval",
// config: &Config{
// ScrapeInterval: time.Minute,
// RSSs: []RSS{
// {
// Name: "test",
// URL: "http://example.com",
// Labels: map[string]string{},
// },
// },
// },
// want: &Config{
// ScrapeInterval: time.Minute,
// RSSs: []RSS{
// {
// Name: "test",
// URL: "http://example.com",
// ScrapeInterval: time.Minute,
// Labels: map[string]string{},
// },
// },
// },
// },
// }
// for _, tt := range tests {
// t.Run(tt.scenario, func(t *testing.T) {
// err := tt.config.Validate()
// if tt.wantErr != "" {
// Expect(err).To(HaveOccurred())
// Expect(err.Error()).To(ContainSubstring(tt.wantErr))
// } else {
// Expect(err).NotTo(HaveOccurred())
// if tt.want != nil {
// Expect(tt.config).To(Equal(tt.want))
// }
// }
// })
// }
// }
// // func TestManager_Run(t *testing.T) {
// // RegisterTestingT(t)
// // tests := []struct {
// // scenario string
// // given string
// // when string
// // then string
// // config *Config
// // mockSetup func(*mockScraperFactory)
// // wantErr string
// // }{
// // {
// // scenario: "Basic Run",
// // given: "a valid configuration with one RSS source",
// // when: "running the manager",
// // then: "should start scraper successfully",
// // config: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // },
// // },
// // },
// // mockSetup: func(f *mockScraperFactory) {
// // mockScraper := scraper.NewMock()
// // mockScraper.On("Config").Return(&scraper.Config{})
// // mockScraper.On("Run").Return()
// // mockScraper.On("Stop").Return()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper, nil)
// // },
// // },
// // {
// // scenario: "Scraper Creation Failure",
// // given: "a configuration that causes scraper creation to fail",
// // when: "running the manager",
// // then: "should return error",
// // config: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // },
// // },
// // },
// // mockSetup: func(f *mockScraperFactory) {
// // f.On("New", mock.Anything, mock.Anything).Return(nil, errors.New("scraper creation failed"))
// // },
// // wantErr: "creating RSS scraper",
// // },
// // }
// // for _, tt := range tests {
// // t.Run(tt.scenario, func(t *testing.T) {
// // mockReader := feedreader.NewMock()
// // mockWriter := feedwriter.NewMock()
// // mockDB := db.New(mockWriter, mockReader)
// // m, err := NewManager(tt.config, mockDB)
// // Expect(err).NotTo(HaveOccurred())
// // mockFactory := newMockScraperFactory()
// // defer mockFactory.AssertExpectations(t)
// // if tt.mockSetup != nil {
// // tt.mockSetup(mockFactory)
// // }
// // mgr := m.(*manager)
// // mgr.rssScraperFactory = mockFactory
// // ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
// // defer cancel()
// // err = m.Run(ctx)
// // if tt.wantErr != "" {
// // Expect(err).To(HaveOccurred())
// // Expect(err.Error()).To(ContainSubstring(tt.wantErr))
// // } else {
// // Expect(err).NotTo(HaveOccurred())
// // }
// // })
// // }
// // }
// // func TestManager_Reload(t *testing.T) {
// // RegisterTestingT(t)
// // tests := []struct {
// // scenario string
// // given string
// // when string
// // then string
// // initConfig *Config
// // newConfig *Config
// // mockSetup func(*mockScraperFactory)
// // validate func(Manager)
// // wantErr string
// // }{
// // {
// // scenario: "Valid Config Update",
// // given: "a running manager and valid new configuration",
// // when: "reloading with new config",
// // then: "should update scrapers successfully",
// // initConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test1",
// // URL: "http://example1.com",
// // },
// // },
// // },
// // },
// // newConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test1",
// // URL: "http://example1.com",
// // },
// // },
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test2",
// // URL: "http://example2.com",
// // },
// // },
// // },
// // },
// // mockSetup: func(f *mockScraperFactory) {
// // mockScraper1 := scraper.NewMock()
// // mockScraper1.On("Config").Return(&scraper.Config{
// // Interval: time.Second,
// // RetentionPeriod: 24 * time.Hour,
// // })
// // mockScraper1.On("Run").Return()
// // mockScraper1.On("Stop").Return()
// // mockScraper2 := scraper.NewMock()
// // mockScraper2.On("Config").Return(&scraper.Config{
// // Interval: time.Second,
// // RetentionPeriod: 24 * time.Hour,
// // })
// // mockScraper2.On("Run").Return()
// // mockScraper2.On("Stop").Return()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper1, nil).Once()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper2, nil).Once()
// // },
// // validate: func(m Manager) {
// // mgr := m.(*manager)
// // Expect(mgr.scrapers).To(HaveLen(2))
// // for id := range mgr.scrapers {
// // Expect(id).To(BeElementOf([]string{"rss/Test/test1", "rss/Test/test2"}))
// // }
// // },
// // },
// // {
// // scenario: "Invalid New Config",
// // given: "a running manager and invalid new configuration",
// // when: "reloading with invalid config",
// // then: "should return validation error",
// // initConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // },
// // },
// // },
// // newConfig: &Config{
// // ScrapeInterval: time.Millisecond,
// // },
// // mockSetup: func(f *mockScraperFactory) {
// // mockScraper := scraper.NewMock()
// // mockScraper.On("Config").Return(&scraper.Config{})
// // mockScraper.On("Run").Return()
// // mockScraper.On("Stop").Return()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper, nil).Maybe()
// // },
// // wantErr: "scrape interval must be at least 1 second",
// // },
// // {
// // scenario: "Keep Unchanged Scraper",
// // given: "a running scraper with unchanged config",
// // when: "reloading with same config",
// // then: "should keep the same scraper instance",
// // initConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // },
// // },
// // },
// // newConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // },
// // },
// // },
// // mockSetup: func(f *mockScraperFactory) {
// // mockScraper := scraper.NewMock()
// // mockScraper.On("Config").Return(&scraper.Config{
// // Interval: time.Second,
// // RetentionPeriod: 24 * time.Hour,
// // })
// // mockScraper.On("Run").Return()
// // mockScraper.On("Stop").Return()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper, nil).Once()
// // },
// // validate: func(m Manager) {
// // mgr := m.(*manager)
// // Expect(mgr.scrapers).To(HaveLen(1))
// // Expect(mgr.scrapers["rss/Test/test"].Config().Interval).To(Equal(time.Second))
// // },
// // },
// // {
// // scenario: "Stop Removed Scraper",
// // given: "a running scraper",
// // when: "reloading with empty config",
// // then: "should stop and remove the scraper",
// // initConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // },
// // },
// // },
// // newConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{},
// // },
// // mockSetup: func(f *mockScraperFactory) {
// // mockScraper := scraper.NewMock()
// // mockScraper.On("Config").Return(&scraper.Config{
// // Interval: time.Second,
// // RetentionPeriod: 24 * time.Hour,
// // })
// // mockScraper.On("Run").Return()
// // mockScraper.On("Stop").Return().Once()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper, nil).Once()
// // },
// // validate: func(m Manager) {
// // mgr := m.(*manager)
// // Expect(mgr.scrapers).To(BeEmpty())
// // },
// // },
// // {
// // scenario: "Restart Changed Scraper",
// // given: "a running scraper",
// // when: "reloading with modified config",
// // then: "should stop old scraper and start new one",
// // initConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // Scrape: &scraper.Config{
// // Interval: time.Second,
// // },
// // },
// // },
// // },
// // newConfig: &Config{
// // ScrapeInterval: time.Second,
// // RSSs: []RSS{
// // {
// // Config: &rss.Config{
// // WebsiteName: "Test",
// // Name: "test",
// // URL: "http://example.com",
// // },
// // Scrape: &scraper.Config{
// // Interval: time.Minute,
// // },
// // },
// // },
// // },
// // mockSetup: func(f *mockScraperFactory) {
// // mockScraper1 := scraper.NewMock()
// // mockScraper1.On("Config").Return(&scraper.Config{
// // Interval: time.Second,
// // RetentionPeriod: 24 * time.Hour,
// // })
// // mockScraper1.On("Run").Return()
// // mockScraper1.On("Stop").Return().Once()
// // mockScraper2 := scraper.NewMock()
// // mockScraper2.On("Config").Return(&scraper.Config{
// // Interval: time.Minute,
// // })
// // mockScraper2.On("Run").Return()
// // mockScraper2.On("Stop").Return()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper1, nil).Once()
// // f.On("New", mock.Anything, mock.Anything).Return(mockScraper2, nil).Once()
// // },
// // validate: func(m Manager) {
// // mgr := m.(*manager)
// // Expect(mgr.scrapers).To(HaveLen(1))
// // Expect(mgr.scrapers["rss/Test/test"].Config().Interval).To(Equal(time.Minute))
// // },
// // },
// // }
// // for _, tt := range tests {
// // t.Run(tt.scenario, func(t *testing.T) {
// // mockReader := feedreader.NewMock()
// // mockWriter := feedwriter.NewMock()
// // mockDB := db.New(mockWriter, mockReader)
// // m, err := NewManager(tt.initConfig, mockDB)
// // Expect(err).NotTo(HaveOccurred())
// // mgr := m.(*manager)
// // mockFactory := newMockScraperFactory()
// // defer mockFactory.AssertExpectations(t)
// // if tt.mockSetup != nil {
// // tt.mockSetup(mockFactory)
// // }
// // mgr.rssScraperFactory = mockFactory
// // ctx, cancel := context.WithCancel(context.Background())
// // defer cancel()
// // go func() {
// // _ = m.Run(ctx)
// // }()
// // time.Sleep(50 * time.Millisecond)
// // err = m.Reload(tt.newConfig)
// // if tt.wantErr != "" {
// // Expect(err).To(HaveOccurred())
// // Expect(err.Error()).To(ContainSubstring(tt.wantErr))
// // } else {
// // Expect(err).NotTo(HaveOccurred())
// // }
// // if tt.validate != nil {
// // tt.validate(m)
// // }
// // })
// // }
// // }
// // mgr := m.(*manager)
// // mockFactory := newMockScraperFactory()
// // defer mockFactory.AssertExpectations(t)
// // if tt.mockSetup != nil {
// // tt.mockSetup(mockFactory)
// // }
// // mgr.rssScraperFactory = mockFactory
// // ctx, cancel := context.WithCancel(context.Background())
// // defer cancel()
// // go func() {
// // _ = m.Run(ctx)
// // }()
// // time.Sleep(50 * time.Millisecond)
// // err = m.Reload(tt.newConfig)
// // if tt.wantErr != "" {
// // Expect(err).To(HaveOccurred())
// // Expect(err.Error()).To(ContainSubstring(tt.wantErr))
// // } else {
// // Expect(err).NotTo(HaveOccurred())
// // }
// // if tt.validate != nil {
// // tt.validate(m)
// // }
// // })
// // }
// // }