init
This commit is contained in:
560
pkg/scrape/manager_test.go
Normal file
560
pkg/scrape/manager_test.go
Normal file
@@ -0,0 +1,560 @@
|
||||
// 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)
|
||||
// // }
|
||||
// // })
|
||||
// // }
|
||||
// // }
|
||||
Reference in New Issue
Block a user