package rocketchat import ( "crypto/tls" "crypto/x509" "net/http" "net/http/httptest" "net/url" "os" "testing" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "github.com/nicholas-fedor/shoutrrr/internal/testutils" "github.com/nicholas-fedor/shoutrrr/pkg/types" ) var ( service *Service envRocketchatURL *url.URL _ = ginkgo.BeforeSuite(func() { service = &Service{} envRocketchatURL, _ = url.Parse(os.Getenv("SHOUTRRR_ROCKETCHAT_URL")) }) ) // Constants for repeated test values. const ( testTokenA = "tokenA" testTokenB = "tokenB" ) func TestRocketchat(t *testing.T) { gomega.RegisterFailHandler(ginkgo.Fail) ginkgo.RunSpecs(t, "Shoutrrr Rocketchat Suite") } var _ = ginkgo.Describe("the rocketchat service", func() { // Add tests for Initialize() ginkgo.Describe("Initialize method", func() { ginkgo.When("initializing with a valid URL", func() { ginkgo.It("should set logger and config without error", func() { service := &Service{} testURL, _ := url.Parse( "rocketchat://testUser@rocketchat.my-domain.com:5055/" + testTokenA + "/" + testTokenB + "/#testChannel", ) err := service.Initialize(testURL, testutils.TestLogger()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) gomega.Expect(service.Config).NotTo(gomega.BeNil()) gomega.Expect(service.Config.Host).To(gomega.Equal("rocketchat.my-domain.com")) gomega.Expect(service.Config.Port).To(gomega.Equal("5055")) gomega.Expect(service.Config.UserName).To(gomega.Equal("testUser")) gomega.Expect(service.Config.TokenA).To(gomega.Equal(testTokenA)) gomega.Expect(service.Config.TokenB).To(gomega.Equal(testTokenB)) gomega.Expect(service.Config.Channel).To(gomega.Equal("#testChannel")) }) }) ginkgo.When("initializing with an invalid URL", func() { ginkgo.It("should return an error", func() { service := &Service{} testURL, _ := url.Parse("rocketchat://rocketchat.my-domain.com") // Missing tokens err := service.Initialize(testURL, testutils.TestLogger()) gomega.Expect(err).To(gomega.HaveOccurred()) gomega.Expect(err). To(gomega.Equal(ErrNotEnoughArguments)) // Updated to use the error variable }) }) }) // Add tests for Send() ginkgo.Describe("Send method", func() { var ( mockServer *httptest.Server service *Service client *http.Client ) ginkgo.BeforeEach(func() { // Create TLS server mockServer = httptest.NewTLSServer(nil) // Handler set in each test // Configure client to trust the mock server's certificate certPool := x509.NewCertPool() for _, cert := range mockServer.TLS.Certificates { certPool.AddCert(cert.Leaf) } client = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ RootCAs: certPool, MinVersion: tls.VersionTLS12, // Explicitly set minimum TLS version to 1.2 }, }, } service = &Service{ Config: &Config{}, Client: client, // Assign the custom client here } service.SetLogger(testutils.TestLogger()) }) ginkgo.AfterEach(func() { if mockServer != nil { mockServer.Close() } }) ginkgo.When("sending a message to a mock server with success", func() { ginkgo.It("should return no error", func() { mockServer.Config.Handler = http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }, ) mockURL, _ := url.Parse(mockServer.URL) service.Config.Host = mockURL.Hostname() service.Config.Port = mockURL.Port() service.Config.TokenA = testTokenA service.Config.TokenB = testTokenB err := service.Send("test message", nil) gomega.Expect(err).NotTo(gomega.HaveOccurred()) }) }) ginkgo.When("sending a message to a mock server with failure", func() { ginkgo.It("should return an error with status code and body", func() { mockServer.Config.Handler = http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("bad request")) }, ) mockURL, _ := url.Parse(mockServer.URL) service.Config.Host = mockURL.Hostname() service.Config.Port = mockURL.Port() service.Config.TokenA = testTokenA service.Config.TokenB = testTokenB err := service.Send("test message", nil) gomega.Expect(err).To(gomega.HaveOccurred()) gomega.Expect(err.Error()). To(gomega.ContainSubstring("notification failed: 400 bad request")) }) }) ginkgo.When("sending a message to an unreachable server", func() { ginkgo.It("should return a connection error", func() { service.Client = http.DefaultClient // Reset to default client for this test service.Config.Host = "nonexistent.domain" service.Config.TokenA = testTokenA service.Config.TokenB = testTokenB err := service.Send("test message", nil) gomega.Expect(err).To(gomega.HaveOccurred()) gomega.Expect(err.Error()).To(gomega.ContainSubstring("posting to URL")) }) }) ginkgo.When("sending a message with params overriding username and channel", func() { ginkgo.It("should use params values in the payload", func() { mockServer.Config.Handler = http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }, ) mockURL, _ := url.Parse(mockServer.URL) service.Config.Host = mockURL.Hostname() service.Config.Port = mockURL.Port() service.Config.TokenA = testTokenA service.Config.TokenB = testTokenB service.Config.UserName = "defaultUser" service.Config.Channel = "#defaultChannel" params := types.Params{ "username": "overrideUser", "channel": "#overrideChannel", } err := service.Send("test message", ¶ms) gomega.Expect(err).NotTo(gomega.HaveOccurred()) // Note: We can't directly inspect the payload here without mocking CreateJSONPayload, // but this ensures the params path is exercised. }) }) }) // Add tests for GetURL() and SetURL() ginkgo.Describe("the rocketchat config", func() { ginkgo.When("generating a URL from a config with all fields", func() { ginkgo.It("should construct a correct URL", func() { config := &Config{ Host: "rocketchat.my-domain.com", Port: "5055", TokenA: testTokenA, TokenB: testTokenB, } url := config.GetURL() gomega.Expect(url.String()). To(gomega.Equal("rocketchat://rocketchat.my-domain.com:5055/" + testTokenA + "/" + testTokenB)) }) }) ginkgo.When("generating a URL from a config without port", func() { ginkgo.It("should construct a correct URL without port", func() { config := &Config{ Host: "rocketchat.my-domain.com", TokenA: testTokenA, TokenB: testTokenB, } url := config.GetURL() gomega.Expect(url.String()). To(gomega.Equal("rocketchat://rocketchat.my-domain.com/" + testTokenA + "/" + testTokenB)) }) }) ginkgo.When("setting URL with a channel starting with @", func() { ginkgo.It("should set channel without adding #", func() { config := &Config{} testURL, _ := url.Parse( "rocketchat://rocketchat.my-domain.com/" + testTokenA + "/" + testTokenB + "/@user", ) err := config.SetURL(testURL) gomega.Expect(err).NotTo(gomega.HaveOccurred()) gomega.Expect(config.Channel).To(gomega.Equal("@user")) }) }) ginkgo.When("setting URL with a regular channel without fragment", func() { ginkgo.It("should prepend # to the channel", func() { config := &Config{} testURL, _ := url.Parse( "rocketchat://rocketchat.my-domain.com/" + testTokenA + "/" + testTokenB + "/general", ) err := config.SetURL(testURL) gomega.Expect(err).NotTo(gomega.HaveOccurred()) gomega.Expect(config.Channel).To(gomega.Equal("#general")) }) }) }) // Add test for GetID() ginkgo.Describe("GetID method", func() { ginkgo.It("should return the correct scheme", func() { service := &Service{} id := service.GetID() gomega.Expect(id).To(gomega.Equal(Scheme)) }) }) })