// 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 . 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) // // } // // }) // // } // // }