├── logo.png
├── go.mod
├── .gitignore
├── test.env
├── go.sum
├── .github
└── workflows
│ └── gotest.yml
├── CONTRIBUTING.md
├── LICENSE
├── alert.go
├── email.go
├── README.md
├── throttler.go
├── ms_teams.go
├── alert_test.go
└── throttler_test.go
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rakutentech/go-alertnotification/HEAD/logo.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/rakutentech/go-alertnotification/v2
2 |
3 | go 1.21
4 |
5 | require (
6 | github.com/GitbookIO/diskache v0.0.0-20161028144708-bfb81bf58cb1
7 | github.com/joho/godotenv v1.5.1
8 | )
9 |
10 | require github.com/GitbookIO/syncgroup v0.0.0-20200915204659-4f0b2961ab10 // indirect
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 | # diskcache directory
14 | cache/*
15 | vendor/*
16 |
17 |
--------------------------------------------------------------------------------
/test.env:
--------------------------------------------------------------------------------
1 | SMTP_HOST=localhost
2 | SMTP_PORT=25
3 | EMAIL_SENDER=test@example.com
4 | EMAIL_RECEIVERS=receiver.test@exmaple.com
5 | EMAIL_ALERT_ENABLED=true
6 | MS_TEAMS_ALERT_ENABLED=
7 |
8 | MS_TEAMS_CARD_SUBJECT=test subject
9 | ALERT_THEME_COLOR=ff5864
10 | ALERT_CARD_SUBJECT=Errror card
11 | MS_TEAMS_CARD_SUBJECT=teams card
12 | APP_ENV=local
13 | APP_NAME=golang
14 | MS_TEAMS_WEBHOOK=Teams webhook
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/GitbookIO/diskache v0.0.0-20161028144708-bfb81bf58cb1 h1:1ui53h0HCYjyrlza+yY+sQsulJdHdYL/xdVWIH3UsyE=
2 | github.com/GitbookIO/diskache v0.0.0-20161028144708-bfb81bf58cb1/go.mod h1:TTHndD25/UJVOyBl/vOq2g5RIg4bidGlmtzb+4Zr+Nw=
3 | github.com/GitbookIO/syncgroup v0.0.0-20200915204659-4f0b2961ab10 h1:G9KsBi5RxXROehPm+TSvTrFXShD613GLKrv9ctY1hFE=
4 | github.com/GitbookIO/syncgroup v0.0.0-20200915204659-4f0b2961ab10/go.mod h1:QEGLOlzj5q/UbkPM0viAulgbdRUpsU3/6HVA9YUA9BU=
5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
7 |
--------------------------------------------------------------------------------
/.github/workflows/gotest.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: Test
3 | jobs:
4 | test:
5 | strategy:
6 | matrix:
7 | go-version: [mod, dev-latest]
8 | os: [ubuntu-latest]
9 | runs-on: ${{ matrix.os }}
10 | env:
11 | GO111MODULE: on
12 | steps:
13 | - name: Cancel Previous Runs
14 | uses: styfle/cancel-workflow-action@0.9.1
15 | with:
16 | access_token: ${{ github.token }}
17 | - uses: actions/checkout@v2
18 | - uses: kevincobain2000/action-gobrew@v2
19 | with:
20 | go-version: ${{ matrix.go }}
21 | - name: Test
22 | run: go test -v ./...
23 | - name: Vet
24 | run: go vet -v ./...
25 | - name: Run Gosec Security Scanner
26 | uses: securego/gosec@master
27 | with:
28 | args: ./...
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Found a Bug?
4 |
5 | If you find a bug in the source code, you can help us by submitting an issue to our GitHub Repository. Even better, you can submit a Pull Request with a fix.
6 |
7 | ## Coding Guidelines
8 |
9 | - All features or bug fixes must be tested by one or more unit tests/specs
10 | - All public API methods must be documented and potentially also described in the user guide.
11 |
12 |
13 | ## Pull Requests
14 |
15 | 1. Fork the project
16 | 2. Implement feature/fix bug & add test cases
17 | 3. Ensure test cases & static analysis runs succesfully
18 | 4. Submit a pull request to develop branch
19 |
20 | **NOTE:** When submitting a pull request, *please make sure to target the `develop` branch*, so that your changes are up-to-date and easy to integrate with the most recent work on the buildpack. Thanks!
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Rakuten, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/alert.go:
--------------------------------------------------------------------------------
1 | package alertnotification
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | )
7 |
8 | // Alert struct for specify the ignoring error and the occuring error
9 | type Alert struct {
10 | Error error
11 | DoNotAlertErrors []error
12 | Expandos *Expandos
13 | }
14 |
15 | // NewAlert creates Alert struct instance
16 | func NewAlert(err error, doNotAlertErrors []error) Alert {
17 | a := Alert{
18 | Error: err,
19 | DoNotAlertErrors: doNotAlertErrors,
20 | Expandos: nil,
21 | }
22 | return a
23 | }
24 |
25 | // NewAlertWithExpandos creates Alert struct instance with expandos
26 | func NewAlertWithExpandos(err error, doNotAlertErrors []error, expandos *Expandos) Alert {
27 | a := Alert{
28 | Error: err,
29 | DoNotAlertErrors: doNotAlertErrors,
30 | Expandos: expandos,
31 | }
32 | return a
33 | }
34 |
35 | // Expandos struct for body and subject
36 | type Expandos struct {
37 | EmailBody string
38 | EmailSubject string
39 | MsTeamsAlertCardSubject string
40 | MsTeamsCardSubject string
41 | MsTeamsError string
42 | }
43 |
44 | // AlertNotification is interface that all send notification function satify including send email
45 | type AlertNotification interface {
46 | Send() error
47 | }
48 |
49 | // DoSendNotification is to send the alert to the specified implemenation of the AlertNoticication interface
50 | func DoSendNotification(alert AlertNotification) error {
51 | return alert.Send()
52 | }
53 |
54 | // Notify send and do throttling when error occur
55 | func (a *Alert) Notify() (err error) {
56 | if a.shouldAlert() {
57 | err := a.dispatch()
58 | fmt.Println(err)
59 | if err != nil {
60 | return err
61 | }
62 | }
63 | return
64 | }
65 |
66 | // Dispatch sends all notification to all registered chanel
67 | func (a *Alert) dispatch() (err error) {
68 | if shouldMail() {
69 | fmt.Println("Send mail....")
70 | e := NewEmailConfig(a.Error, a.Expandos)
71 | err := e.Send()
72 | if err != nil {
73 | return err
74 | }
75 | }
76 |
77 | if shouldMsTeams() {
78 | fmt.Println("SendTeams")
79 | m := NewMsTeam(a.Error, a.Expandos)
80 | err := m.Send()
81 | if err != nil {
82 | return err
83 | }
84 | }
85 | return
86 | }
87 |
88 | func (a *Alert) shouldAlert() bool {
89 | if !a.isThrottlingEnabled() {
90 | //Always alert when throttling is disabled.
91 | return true
92 | }
93 |
94 | if a.isDoNotAlert() {
95 | return false
96 | }
97 | t := NewThrottler()
98 | return !t.IsThrottledOrGraced(a.Error)
99 | }
100 |
101 | func (a *Alert) isDoNotAlert() bool {
102 | for _, e := range a.DoNotAlertErrors {
103 | if e.Error() == a.Error.Error() {
104 | return true
105 | }
106 | }
107 | return false
108 | }
109 |
110 | func shouldMsTeams() bool {
111 | return os.Getenv("MS_TEAMS_ALERT_ENABLED") == "true"
112 | }
113 |
114 | func shouldMail() bool {
115 | return os.Getenv("EMAIL_ALERT_ENABLED") == "true"
116 | }
117 |
118 | func (a *Alert) isThrottlingEnabled() bool {
119 | return os.Getenv("THROTTLE_ENABLED") != "false"
120 | }
121 |
122 | // RemoveCurrentThrotting remove all current throttlings.
123 | func (a *Alert) RemoveCurrentThrotting() error {
124 | t := NewThrottler()
125 | return t.CleanThrottlingCache()
126 | }
127 |
--------------------------------------------------------------------------------
/email.go:
--------------------------------------------------------------------------------
1 | package alertnotification
2 |
3 | import (
4 | "encoding/base64"
5 | "errors"
6 | "fmt"
7 | "net/smtp"
8 | "os"
9 | "strings"
10 | )
11 |
12 | // EmailConfig is email setting struct
13 | type EmailConfig struct {
14 | Username string
15 | Password string
16 | Host string
17 | Port string
18 | Sender string
19 | EnvelopeFrom string
20 | Receivers []string // Can use comma for multiple email
21 | Subject string
22 | ErrorObj error
23 | Expandos *Expandos // can modify mail subject and content on demand
24 | }
25 |
26 | func getReceivers() []string {
27 | delimeter := ","
28 | receivers := os.Getenv("EMAIL_RECEIVERS")
29 | if len(receivers) == 0 {
30 | return nil
31 | }
32 | return strings.Split(receivers, delimeter)
33 | }
34 |
35 | // NewEmailConfig create new EmailConfig struct
36 | func NewEmailConfig(err error, expandos *Expandos) EmailConfig {
37 | config := EmailConfig{
38 | Username: os.Getenv("EMAIL_USERNAME"),
39 | Password: os.Getenv("EMAIL_PASSWORD"),
40 | Host: os.Getenv("SMTP_HOST"),
41 | Port: os.Getenv("SMTP_PORT"),
42 | Sender: os.Getenv("EMAIL_SENDER"),
43 | EnvelopeFrom: os.Getenv("EMAIL_ENVELOPE_FROM"),
44 | Subject: os.Getenv("EMAIL_SUBJECT"),
45 | Receivers: getReceivers(),
46 | ErrorObj: err,
47 | Expandos: expandos,
48 | }
49 | if len(strings.TrimSpace(config.EnvelopeFrom)) == 0 {
50 | config.EnvelopeFrom = config.Sender
51 | }
52 | return config
53 | }
54 |
55 | // Send Alert email
56 | func (ec *EmailConfig) Send() error {
57 | fmt.Println("sending email ....")
58 | var err error
59 | if ec.Receivers == nil {
60 | return errors.New("notification receivers are empty")
61 | }
62 | r := strings.NewReplacer("\r\n", "", "\r", "", "\n", "", "%0a", "", "%0d", "")
63 |
64 | messageDetail := "Error: \r\n" + fmt.Sprintf("%+v", ec.ErrorObj)
65 |
66 | // update body and subject dynamically
67 | if ec.Expandos != nil {
68 | if ec.Expandos.EmailBody != "" {
69 | messageDetail = ec.Expandos.EmailBody
70 | }
71 | if ec.Expandos.EmailSubject != "" {
72 | ec.Subject = ec.Expandos.EmailSubject
73 | }
74 | }
75 |
76 | message := "To: " + strings.Join(ec.Receivers, ", ") + "\r\n" +
77 | "From: " + ec.Sender + "\r\n" +
78 | "Subject: " + ec.Subject + "\r\n" +
79 | "Content-Type: text/html; charset=\"UTF-8\"\r\n" +
80 | "Content-Transfer-Encoding: base64\r\n" +
81 | "\r\n" + base64.StdEncoding.EncodeToString([]byte(messageDetail))
82 |
83 | if len(strings.TrimSpace(ec.Username)) != 0 {
84 | stmpAuth := smtp.PlainAuth("", ec.Username, ec.Password, ec.Host)
85 |
86 | err = smtp.SendMail(
87 | ec.Host+":"+ec.Port,
88 | stmpAuth,
89 | ec.EnvelopeFrom,
90 | ec.Receivers,
91 | []byte(message),
92 | )
93 | return err
94 | }
95 | fmt.Println("Send with localhost. ......")
96 | conn, err := smtp.Dial(ec.Host + ":" + ec.Port)
97 | if err != nil {
98 | return err
99 | }
100 |
101 | defer conn.Close()
102 | if err = conn.Mail(r.Replace(ec.EnvelopeFrom)); err != nil {
103 | return err
104 | }
105 | // format receiver email
106 | for i := range ec.Receivers {
107 | ec.Receivers[i] = r.Replace(ec.Receivers[i])
108 | if err = conn.Rcpt(ec.Receivers[i]); err != nil {
109 | return err
110 | }
111 | }
112 |
113 | w, err := conn.Data()
114 | if err != nil {
115 | return err
116 | }
117 | _, err = w.Write([]byte(message))
118 | if err != nil {
119 | return err
120 | }
121 | err = w.Close()
122 | if err != nil {
123 | return err
124 | }
125 | return conn.Quit()
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Send Alert Notifications for Go Errors.
Notify when new error arrives.
8 |
9 |
10 | Throttle notifications to avoid overwhelming your inbox.
11 |
12 | Supports multiple Emails, MS Teams and proxy support.
13 |
14 |
15 | ## Usage
16 |
17 | ```bash
18 | go install github.com/rakutentech/go-alertnotification@latest
19 | ```
20 |
21 | ## Configurations
22 |
23 | This package use golang env variables as settings.
24 |
25 | ### General Configs
26 |
27 |
28 | | Env Variable | default | Description |
29 | | :----------- | :------ | :------------------------------------------------------------ |
30 | | APP_ENV | | application environment to be appeared in email/teams message |
31 | | APP_NAME | | application name to be appeared in email/teams message |
32 |
33 |
34 | ### Email Configs
35 |
36 | | Env Variable | default | Description |
37 | | :------------------ | :------ | :------------------------------------------------------------------------------ |
38 | | **EMAIL_SENDER** | | **required** sender email address |
39 | | **EMAIL_RECEIVERS** | | **required** receiver email addresses. Eg. `test1@gmail.com`,`test2@gmail.com` |
40 | | EMAIL_ALERT_ENABLED | false | change to "true" to enable |
41 | | SMTP_HOST | | SMTP server hostname |
42 | | SMTP_PORT | | SMTP server port |
43 | | EMAIL_USERNAME | | SMTP username |
44 | | EMAIL_PASSWORD | | SMTP password |
45 |
46 | ### Ms Teams Configs
47 |
48 | | Env Variable | default | Description |
49 | | :--------------------- | :------ | :----------------------------- |
50 | | **MS_TEAMS_WEBHOOK** | | **required** Ms Teams webhook. |
51 | | MS_TEAMS_ALERT_ENABLED | false | change to "true" to enable |
52 | | MS_TEAMS_CARD_SUBJECT | | MS teams card subject |
53 | | ALERT_CARD_SUBJECT | | Alert MessageCard subject |
54 | | ALERT_THEME_COLOR | | Themes color |
55 | | MS_TEAMS_PROXY_URL | | Work behind corporate proxy |
56 |
57 | ### Throttling Configs
58 |
59 | | Env Variable | default | Explanation |
60 | | :--------------------- | :------------------------------------------- | :----------------------------- |
61 | | THROTTLE_DURATION | 7 | throttling duration in minutes |
62 | | THROTTLE_GRACE_SECONDS | 0 | throttling grace in seconds |
63 | | THROTTLE_DISKCACHE_DIR | `/tmp/cache/{APP_NAME}_throttler_disk_cache` | disk location for throttling |
64 | | THROTTLE_ENABLED | true | Disable all together |
65 |
66 | ## Usage
67 |
68 | ### Simple
69 |
70 | ```go
71 | //import
72 | import n "github.com/rakutentech/go-alertnotification"
73 | err := errors.New("Alert me")
74 | ignoringErrs := []error{errors.New("Ignore 001"), errors.New("Ignore 002")};
75 |
76 | //Create New Alert
77 | alert := n.NewAlert(err, ignoringErrs)
78 | //Send notification
79 | alert.Notify()
80 | ```
81 |
82 | ### With customized fields
83 |
84 | ```go
85 | import n "github.com/rakutentech/go-alertnotification"
86 |
87 | //Create expandos, can keep the field value as configured by removing that field from expandos
88 | expandos := &n.Expandos{
89 | EmailBody: "This is the customized email body",
90 | EmailSubject: "This is the customized email subject",
91 | MsTeamsCardSubject: "This is the customized MS Teams card summary",
92 | MsTeamsAlertCardSubject: "This is the customized MS Teams card title",
93 | MsTeamsError: "This is the customized MS Teams card error message",
94 | }
95 |
96 | //Create New Alert
97 | alert := n.NewAlertWithExpandos(err, ignoringErr, expandos)
98 |
99 | //Send notification
100 | alert.Notify()
101 |
102 | // To remove all current throttling
103 | alert.RemoveCurrentThrotting()
104 |
105 | ```
--------------------------------------------------------------------------------
/throttler.go:
--------------------------------------------------------------------------------
1 | package alertnotification
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strconv"
7 | "time"
8 |
9 | "github.com/GitbookIO/diskache"
10 | )
11 |
12 | // Throttler struct storing disckage directory and Throttling duration
13 | type Throttler struct {
14 | CacheOpt string
15 | ThrottleDuration int
16 | GraceDuration int
17 | }
18 |
19 | // ErrorOccurrence store error time and error
20 | type ErrorOccurrence struct {
21 | StartTime time.Time
22 | ErrorType error
23 | }
24 |
25 | // NewThrottler constructs new Throttle struct and init diskcache directory
26 | func NewThrottler() Throttler {
27 |
28 | t := Throttler{
29 | CacheOpt: fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME")),
30 | ThrottleDuration: 5, // default 5mn
31 | GraceDuration: 0, // default 0sc
32 | }
33 | if len(os.Getenv("THROTTLE_DURATION")) != 0 {
34 | duration, err := strconv.Atoi(os.Getenv("THROTTLE_DURATION"))
35 | if err != nil {
36 | return t
37 | }
38 | t.ThrottleDuration = duration
39 | }
40 | if len(os.Getenv("THROTTLE_GRACE_SECONDS")) != 0 {
41 | grace, err := strconv.Atoi(os.Getenv("THROTTLE_GRACE_SECONDS"))
42 | if err != nil {
43 | return t
44 | }
45 | t.GraceDuration = grace
46 | }
47 |
48 | if len(os.Getenv("THROTTLE_DISKCACHE_DIR")) != 0 {
49 | t.CacheOpt = os.Getenv("THROTTLE_DISKCACHE_DIR")
50 | }
51 |
52 | return t
53 | }
54 |
55 | // IsThrottled checks if the error has been throttled. If not, throttle it
56 | func (t *Throttler) IsThrottledOrGraced(ocError error) bool {
57 | dc, err := t.getDiskCache()
58 | if err != nil {
59 | return false
60 | }
61 | cachedThrottleTime, throttled := dc.Get(ocError.Error())
62 | cachedDetectionTime, graced := dc.Get(fmt.Sprintf("%v_detectionTime", ocError.Error()))
63 |
64 | throttleIsOver := isOverThrottleDuration(string(cachedThrottleTime), t.ThrottleDuration)
65 | if throttled && !throttleIsOver {
66 | // already throttled and not over throttling duration, do nothing
67 | return true
68 | }
69 |
70 | if !graced || isOverGracePlusThrottleDuration(string(cachedDetectionTime), t.GraceDuration, t.ThrottleDuration) {
71 | cachedDetectionTime = t.InitGrace(ocError)
72 | }
73 | if cachedDetectionTime != nil && !isOverGraceDuration(string(cachedDetectionTime), t.GraceDuration) {
74 | // grace duration is not over yet, do nothing
75 | return true
76 | }
77 |
78 | // if it has not throttled yet or over throttle duration, throttle it and return false to send notification
79 | // Rethrottler will also renew the timestamp in the throttler cache.
80 | if err = t.ThrottleError(ocError); err != nil {
81 | return false
82 | }
83 | return false
84 | }
85 |
86 | func isOverGracePlusThrottleDuration(cachedTime string, graceDurationInSec int, throttleDurationInMin int) bool {
87 | detectionTime, err := time.Parse(time.RFC3339, string(cachedTime))
88 | if err != nil {
89 | return false
90 | }
91 | now := time.Now()
92 | diff := int(now.Sub(detectionTime).Seconds())
93 | overallDurationInSec := graceDurationInSec + throttleDurationInMin*60
94 | return diff >= overallDurationInSec
95 | }
96 |
97 | func isOverGraceDuration(cachedTime string, graceDuration int) bool {
98 | detectionTime, err := time.Parse(time.RFC3339, string(cachedTime))
99 | if err != nil {
100 | return false
101 | }
102 | now := time.Now()
103 | diff := int(now.Sub(detectionTime).Seconds())
104 | return diff >= graceDuration
105 | }
106 |
107 | func isOverThrottleDuration(cachedTime string, throttleDuration int) bool {
108 | throttledTime, err := time.Parse(time.RFC3339, string(cachedTime))
109 | if err != nil {
110 | return false
111 | }
112 | now := time.Now()
113 | diff := int(now.Sub(throttledTime).Minutes())
114 | return diff >= throttleDuration
115 | }
116 |
117 | // ThrottleError throttle the alert within the limited duration
118 | func (t *Throttler) ThrottleError(errObj error) error {
119 | dc, err := t.getDiskCache()
120 | if err != nil {
121 | return err
122 | }
123 |
124 | now := time.Now().Format(time.RFC3339)
125 | err = dc.Set(errObj.Error(), []byte(now))
126 |
127 | return err
128 | }
129 |
130 | // ThrottleError throttle the alert within the limited duration
131 | func (t *Throttler) InitGrace(errObj error) []byte {
132 | dc, err := t.getDiskCache()
133 | if err != nil {
134 | return nil
135 | }
136 | now := time.Now().Format(time.RFC3339)
137 | cachedDetectionTime := []byte(now)
138 | err = dc.Set(fmt.Sprintf("%v_detectionTime", errObj.Error()), cachedDetectionTime)
139 | if err != nil {
140 | return nil
141 | }
142 |
143 | return cachedDetectionTime
144 | }
145 |
146 | // CleanThrottlingCache clean all the diskcache in throttling cache directory
147 | func (t *Throttler) CleanThrottlingCache() (err error) {
148 | dc, err := t.getDiskCache()
149 | if err != nil {
150 | return err
151 | }
152 | err = dc.Clean()
153 | return err
154 | }
155 |
156 | func (t *Throttler) getDiskCache() (*diskache.Diskache, error) {
157 | opts := diskache.Opts{
158 | Directory: t.CacheOpt,
159 | }
160 | return diskache.New(&opts)
161 | }
162 |
--------------------------------------------------------------------------------
/ms_teams.go:
--------------------------------------------------------------------------------
1 | package alertnotification
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "time"
13 | )
14 |
15 | // MsTeam is Adaptive Card for Team notification
16 | type MsTeam struct {
17 | Type string `json:"type"`
18 | Attachments []attachment `json:"attachments"`
19 | }
20 |
21 | type attachment struct {
22 | ContentType string `json:"contentType"`
23 | ContentURL *string `json:"contentUrl"`
24 | Content cardContent `json:"content"`
25 | }
26 |
27 | type cardContent struct {
28 | Schema string `json:"$schema"`
29 | Type string `json:"type"`
30 | Version string `json:"version"`
31 | AccentColor string `json:"accentColor"`
32 | Body []interface{} `json:"body"`
33 | Actions []action `json:"actions"`
34 | MSTeams msTeams `json:"msteams"`
35 | }
36 |
37 | type textBlock struct {
38 | Type string `json:"type"`
39 | Text string `json:"text"`
40 | ID string `json:"id,omitempty"`
41 | Size string `json:"size,omitempty"`
42 | Weight string `json:"weight,omitempty"`
43 | Color string `json:"color,omitempty"`
44 | }
45 |
46 | type fact struct {
47 | Title string `json:"title"`
48 | Value string `json:"value"`
49 | }
50 |
51 | type factSet struct {
52 | Type string `json:"type"`
53 | Facts []fact `json:"facts"`
54 | ID string `json:"id"`
55 | }
56 |
57 | type codeBlock struct {
58 | Type string `json:"type"`
59 | CodeSnippet string `json:"codeSnippet"`
60 | FontType string `json:"fontType"`
61 | Wrap bool `json:"wrap"`
62 | }
63 |
64 | type action struct {
65 | Type string `json:"type"`
66 | Title string `json:"title"`
67 | URL string `json:"url"`
68 | }
69 |
70 | type msTeams struct {
71 | Width string `json:"width"`
72 | }
73 |
74 | // NewMsTeam is used to create MsTeam
75 | func NewMsTeam(err error, expandos *Expandos) MsTeam {
76 | title := os.Getenv("ALERT_CARD_SUBJECT")
77 | summary := os.Getenv("MS_TEAMS_CARD_SUBJECT")
78 | errMsg := fmt.Sprintf("%+v", err)
79 | hostname, err := os.Hostname()
80 | if err != nil {
81 | hostname = "hostname_unknown"
82 | }
83 | hostname += " " + os.Getenv("APP_NAME")
84 | // apply expandos on card
85 | if expandos != nil {
86 | if expandos.MsTeamsAlertCardSubject != "" {
87 | title = expandos.MsTeamsAlertCardSubject
88 | }
89 | if expandos.MsTeamsCardSubject != "" {
90 | summary = expandos.MsTeamsCardSubject
91 | }
92 | if expandos.MsTeamsError != "" {
93 | errMsg = expandos.MsTeamsError
94 | }
95 | }
96 |
97 | return MsTeam{
98 | Type: "message",
99 | Attachments: []attachment{
100 | {
101 | ContentType: "application/vnd.microsoft.card.adaptive",
102 | ContentURL: nil,
103 | Content: cardContent{
104 | Schema: "http://adaptivecards.io/schemas/adaptive-card.json",
105 | Type: "AdaptiveCard",
106 | Version: "1.4",
107 | AccentColor: "bf0000",
108 | Body: []interface{}{
109 | textBlock{
110 | Type: "TextBlock",
111 | Text: title,
112 | ID: "title",
113 | Size: "large",
114 | Weight: "bolder",
115 | Color: "accent",
116 | },
117 | factSet{
118 | Type: "FactSet",
119 | Facts: []fact{
120 | {
121 | Title: "Title:",
122 | Value: title,
123 | },
124 | {
125 | Title: "Summary:",
126 | Value: summary,
127 | },
128 | {
129 | Title: "Hostname:",
130 | Value: hostname,
131 | },
132 | },
133 | ID: "acFactSet",
134 | },
135 | codeBlock{
136 | Type: "CodeBlock",
137 | CodeSnippet: errMsg,
138 | FontType: "monospace",
139 | Wrap: true,
140 | },
141 | },
142 | MSTeams: msTeams{
143 | Width: "Full",
144 | },
145 | },
146 | },
147 | },
148 | }
149 | }
150 |
151 | // Send is implementation of interface AlertNotification's Send()
152 | func (card *MsTeam) Send() (err error) {
153 | requestBody, err := json.Marshal(card)
154 | if err != nil {
155 | return err
156 | }
157 |
158 | var client http.Client
159 | timeout := time.Duration(5 * time.Second)
160 | proxyURL := os.Getenv("MS_TEAMS_PROXY_URL")
161 |
162 | if proxyURL != "" {
163 | proxy, err := url.Parse(proxyURL)
164 | if err != nil {
165 | return err
166 | }
167 | transport := &http.Transport{Proxy: http.ProxyURL(proxy)}
168 | client = http.Client{
169 | Transport: transport,
170 | Timeout: timeout,
171 | }
172 | } else {
173 | client = http.Client{
174 | Timeout: timeout,
175 | }
176 | }
177 |
178 | wb := os.Getenv("MS_TEAMS_WEBHOOK")
179 | if len(wb) == 0 {
180 | return errors.New("cannot send alert to MSTeams.MS_TEAMS_WEBHOOK is not set in the environment. ")
181 | }
182 | request, err := http.NewRequest("POST", wb, bytes.NewBuffer(requestBody))
183 | request.Header.Set("Content-type", "application/json")
184 | if err != nil {
185 | return err
186 | }
187 |
188 | resp, err := client.Do(request)
189 | if err != nil {
190 | return err
191 | }
192 |
193 | defer resp.Body.Close()
194 |
195 | if resp.StatusCode != http.StatusAccepted {
196 | respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
197 | if err != nil {
198 | return err
199 | }
200 | return fmt.Errorf("unexpected response from webhook: %s", string(respBody))
201 | }
202 | return
203 | }
204 |
--------------------------------------------------------------------------------
/alert_test.go:
--------------------------------------------------------------------------------
1 | package alertnotification
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 | "testing"
8 |
9 | "github.com/joho/godotenv"
10 | )
11 |
12 | func setEnv() {
13 | if err := godotenv.Load("test.env"); err != nil {
14 | fmt.Println(err)
15 | }
16 | }
17 |
18 | func Test_shouldMsTeams(t *testing.T) {
19 | tests := []struct {
20 | name string
21 | want bool
22 | }{
23 | {name: "shouldSend", want: true},
24 | {name: "shouldNotSend", want: false},
25 | }
26 |
27 | for _, tt := range tests {
28 | setEnv()
29 | switch tt.name {
30 | case "shouldSend":
31 | os.Setenv("EMAIL_ALERT_ENABLED", "true")
32 | os.Setenv("MS_TEAMS_ALERT_ENABLED", "true")
33 | case "shouldNotSend":
34 | os.Setenv("MS_TEAMS_ALERT_ENABLED", "")
35 | }
36 | t.Run(tt.name, func(t *testing.T) {
37 |
38 | if got := shouldMsTeams(); got != tt.want {
39 | t.Errorf("shouldMsTeams() = %v, want %v", got, tt.want)
40 | }
41 | })
42 | }
43 | }
44 |
45 | func Test_shouldMail(t *testing.T) {
46 | tests := []struct {
47 | name string
48 | want bool
49 | }{
50 |
51 | {name: "shouldSend", want: true},
52 | {name: "shouldNotSend", want: false},
53 | }
54 |
55 | for _, tt := range tests {
56 | setEnv()
57 | switch tt.name {
58 | case "shouldSend":
59 | os.Setenv("EMAIL_ALERT_ENABLED", "true")
60 | case "shouldNotSend":
61 | os.Setenv("EMAIL_ALERT_ENABLED", "")
62 | }
63 | t.Run(tt.name, func(t *testing.T) {
64 | if got := shouldMail(); got != tt.want {
65 | t.Errorf("shouldMail() = %v, want %v", got, tt.want)
66 | }
67 | })
68 | }
69 | }
70 |
71 | func TestAlert_Notify(t *testing.T) {
72 | expandos := &Expandos{
73 | EmailBody: "TEST: This is mail body",
74 | EmailSubject: "TEST: This is mail subject",
75 | MsTeamsAlertCardSubject: "TEST: This is MS Teams card title",
76 | MsTeamsCardSubject: "TEST: This is MS Teams card summary",
77 | MsTeamsError: "TEST: This is MS Teams card error message",
78 | }
79 | type fields struct {
80 | Error error
81 | DoNotAlertErrors []error
82 | Expandos *Expandos
83 | }
84 | tests := []struct {
85 | name string
86 | fields fields
87 | wantErr bool
88 | }{
89 | {name: "Notify_false",
90 | fields: fields{
91 | Error: errors.New("Do not alert"), // Do not alert => no error will occur
92 | DoNotAlertErrors: []error{
93 | errors.New("Do not alert"), errors.New("if this error then alert")},
94 | },
95 | wantErr: false,
96 | },
97 | {name: "Notify_true",
98 | fields: fields{
99 | Error: errors.New("give an alert"), // error occured and try to send email => no mail setting configure => error.
100 | DoNotAlertErrors: []error{
101 | errors.New("Do not alert"), errors.New("if this error then alert")},
102 | },
103 | wantErr: true,
104 | },
105 | {name: "Expandos",
106 | fields: fields{
107 | Error: errors.New("give an alert"), // error occured and try to send email => no mail setting configure => error.
108 | DoNotAlertErrors: []error{
109 | errors.New("Do not alert"), errors.New("if this error then alert")},
110 | Expandos: expandos,
111 | },
112 | wantErr: true,
113 | },
114 | }
115 | for _, tt := range tests {
116 | if err := godotenv.Overload("test.env"); err != nil { // Reload Env
117 | fmt.Println(err)
118 | }
119 | t.Run(tt.name, func(t *testing.T) {
120 |
121 | a := &Alert{
122 | Error: tt.fields.Error,
123 | DoNotAlertErrors: tt.fields.DoNotAlertErrors,
124 | Expandos: tt.fields.Expandos,
125 | }
126 | if err := a.RemoveCurrentThrotting(); err != nil {
127 | t.Errorf("Alert.Notify() error = %+v", err)
128 | }
129 | if err := a.Notify(); (err != nil) != tt.wantErr {
130 | t.Errorf("Alert.Notify() error = %v, wantErr %v", err, tt.wantErr)
131 | }
132 | })
133 | }
134 | }
135 |
136 | func TestAlert_shouldAlert(t *testing.T) {
137 | type fields struct {
138 | Error error
139 | DoNotAlertErrors []error
140 | }
141 | tests := []struct {
142 | name string
143 | fields fields
144 | want bool
145 | }{
146 | {name: "shouldAlert_false",
147 | fields: fields{
148 | Error: errors.New("Do not alert"),
149 | DoNotAlertErrors: []error{
150 | errors.New("Do not alert"), errors.New("if this error then don't alert")},
151 | },
152 | want: false,
153 | },
154 | {name: "shouldAlert_true",
155 | fields: fields{
156 | Error: errors.New("alert this"),
157 | DoNotAlertErrors: []error{
158 | errors.New("do not alert"), errors.New("if this error then don't alert")},
159 | },
160 | want: true,
161 | },
162 | {name: "shouldAlert_graced_false",
163 | fields: fields{
164 | Error: errors.New("alert this"),
165 | DoNotAlertErrors: []error{
166 | errors.New("do not alert"), errors.New("if this error then don't alert")},
167 | },
168 | want: false,
169 | },
170 | {name: "shouldAlert_true_disable_throttling",
171 | fields: fields{
172 | Error: errors.New("do not alert"),
173 | DoNotAlertErrors: []error{
174 | errors.New("do not alert"), errors.New("if this error then don't alert")},
175 | },
176 | want: true,
177 | },
178 | }
179 | for _, tt := range tests {
180 | t.Run(tt.name, func(t *testing.T) {
181 | if tt.name == "shouldAlert_true_disable_throttling" {
182 | os.Setenv("THROTTLE_ENABLED", "false")
183 | }
184 | if tt.name == "shouldAlert_graced_false" {
185 | os.Setenv("THROTTLE_GRACE_SECONDS", "20")
186 | }
187 | a := &Alert{
188 | Error: tt.fields.Error,
189 | DoNotAlertErrors: tt.fields.DoNotAlertErrors,
190 | }
191 | if err := a.RemoveCurrentThrotting(); err != nil {
192 | t.Errorf("Alert.Notify() error = %+v", err)
193 | }
194 | got := a.shouldAlert()
195 | if got != tt.want {
196 | t.Errorf("Alert.shouldAlert() = %v, want %v", got, tt.want)
197 | }
198 | })
199 | }
200 | }
201 |
202 | func TestAlert_isDoNotAlert(t *testing.T) {
203 | type fields struct {
204 | Error error
205 | DoNotAlertErrors []error
206 | }
207 | tests := []struct {
208 | name string
209 | fields fields
210 | want bool
211 | }{
212 | {name: "isDoNotAlert_true",
213 | fields: fields{
214 | Error: errors.New("Do not alert"),
215 | DoNotAlertErrors: []error{
216 | errors.New("Do not alert"), errors.New("if this error then not alert")},
217 | },
218 | want: true,
219 | },
220 | {name: "isDoNotAlert_false",
221 | fields: fields{
222 | Error: errors.New("give an alert"),
223 | DoNotAlertErrors: []error{
224 | errors.New("Do not alert"), errors.New("if this error then do alert")},
225 | },
226 | want: false,
227 | },
228 | }
229 | for _, tt := range tests {
230 | t.Run(tt.name, func(t *testing.T) {
231 | a := &Alert{
232 | Error: tt.fields.Error,
233 | DoNotAlertErrors: tt.fields.DoNotAlertErrors,
234 | }
235 | if got := a.isDoNotAlert(); got != tt.want {
236 | t.Errorf("Alert.isDoNotAlert() = %v, want %v", got, tt.want)
237 | }
238 | })
239 | }
240 | }
241 |
242 | func TestAlert_isThrottlingEnabled(t *testing.T) {
243 | type fields struct {
244 | Error error
245 | DoNotAlertErrors []error
246 | }
247 | tests := []struct {
248 | name string
249 | fields fields
250 | want bool
251 | }{
252 | {name: "isThrottlingEnabled_true",
253 | fields: fields{
254 | Error: errors.New("Do not alert"),
255 | DoNotAlertErrors: []error{
256 | errors.New("Do not alert"), errors.New("if this error then not alert")},
257 | },
258 | want: true,
259 | },
260 | {name: "isThrottlingEnabled_false",
261 | fields: fields{
262 | Error: errors.New("give an alert"),
263 | DoNotAlertErrors: []error{
264 | errors.New("Do not alert"), errors.New("if this error then do alert")},
265 | },
266 | want: false,
267 | },
268 | }
269 | for _, tt := range tests {
270 | t.Run(tt.name, func(t *testing.T) {
271 | os.Setenv("THROTTLE_ENABLED", "true")
272 | if tt.name == "isThrottlingEnabled_false" {
273 | os.Setenv("THROTTLE_ENABLED", "false")
274 | }
275 | a := &Alert{
276 | Error: tt.fields.Error,
277 | DoNotAlertErrors: tt.fields.DoNotAlertErrors,
278 | }
279 | if got := a.isThrottlingEnabled(); got != tt.want {
280 | t.Errorf("Alert.isThrottlingEnabled() = %v, want %v", got, tt.want)
281 | }
282 | })
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/throttler_test.go:
--------------------------------------------------------------------------------
1 | package alertnotification
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 | "reflect"
8 | "testing"
9 | "time"
10 |
11 | "github.com/GitbookIO/diskache"
12 | )
13 |
14 | func TestNewThrottler(t *testing.T) {
15 | tests := []struct {
16 | name string
17 | want Throttler
18 | }{
19 | {
20 | name: "default",
21 | want: Throttler{
22 | CacheOpt: fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME")),
23 | ThrottleDuration: 5,
24 | GraceDuration: 0,
25 | },
26 | },
27 | {
28 | name: "change duration",
29 | want: Throttler{
30 | CacheOpt: fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME")),
31 | ThrottleDuration: 7,
32 | GraceDuration: 5,
33 | },
34 | },
35 | {
36 | name: "change both",
37 | want: Throttler{
38 | CacheOpt: "new_cache_dir",
39 | ThrottleDuration: 8,
40 | GraceDuration: 0,
41 | },
42 | },
43 | }
44 | for _, tt := range tests {
45 | if tt.name == "change duration" {
46 | os.Setenv("THROTTLE_DURATION", "7")
47 | os.Setenv("THROTTLE_GRACE_SECONDS", "5")
48 | } else if tt.name == "change both" {
49 | os.Setenv("THROTTLE_DURATION", "8")
50 | os.Setenv("THROTTLE_GRACE_SECONDS", "0")
51 | os.Setenv("THROTTLE_DISKCACHE_DIR", "new_cache_dir")
52 | } else if tt.name == "default" {
53 | os.Setenv("THROTTLE_GRACE_SECONDS", "")
54 | }
55 | t.Run(tt.name, func(t *testing.T) {
56 | got := NewThrottler()
57 | if !reflect.DeepEqual(got, tt.want) {
58 | t.Errorf("NewThrottler() = %v, want %v", got, tt.want)
59 | }
60 | })
61 | }
62 | }
63 |
64 | func TestThrottler_IsThrottledOrGraced(t *testing.T) {
65 | type fields struct {
66 | CacheOpt string
67 | ThrottleDuration int
68 | GraceDuration int
69 | }
70 | type args struct {
71 | ocError error
72 | }
73 | tests := []struct {
74 | name string
75 | fields fields
76 | args args
77 | want bool
78 | }{
79 | {
80 | name: "default",
81 | fields: fields{
82 | CacheOpt: fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME")),
83 | ThrottleDuration: 5,
84 | GraceDuration: 0,
85 | },
86 | args: args{
87 | ocError: errors.New("test_throttling"),
88 | },
89 | want: false,
90 | },
91 | {
92 | name: "throttled_true",
93 | fields: fields{
94 | CacheOpt: fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME")),
95 | ThrottleDuration: 5,
96 | GraceDuration: 0,
97 | },
98 | args: args{
99 | ocError: errors.New("test_throttling"),
100 | },
101 | want: true,
102 | },
103 | {
104 | name: "graced_true",
105 | fields: fields{
106 | CacheOpt: fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME")),
107 | ThrottleDuration: 5,
108 | GraceDuration: 25,
109 | },
110 | args: args{
111 | ocError: errors.New("test_throttling"),
112 | },
113 | want: true,
114 | },
115 | }
116 | for _, tt := range tests {
117 | t.Run(tt.name, func(t *testing.T) {
118 | th := &Throttler{
119 | CacheOpt: tt.fields.CacheOpt,
120 | ThrottleDuration: tt.fields.ThrottleDuration,
121 | GraceDuration: tt.fields.GraceDuration,
122 | }
123 | if tt.name == "throttled_true" {
124 | if err := th.ThrottleError(tt.args.ocError); err != nil {
125 | t.Errorf("testing failed : %+v", err)
126 | }
127 | }
128 | if got := th.IsThrottledOrGraced(tt.args.ocError); got != tt.want {
129 | t.Errorf("Throttler.IsThrottled() = %v, want %v", got, tt.want)
130 | }
131 | err := th.CleanThrottlingCache()
132 | if err != nil {
133 | t.Errorf("Cannot clean after test. %+v", err)
134 | }
135 |
136 | })
137 | }
138 | }
139 |
140 | func TestThrottler_ThrottleError(t *testing.T) {
141 | type fields struct {
142 | CacheOpt string
143 | ThrottleDuration int
144 | GraceDuration int
145 | }
146 | type args struct {
147 | errObj error
148 | }
149 | tests := []struct {
150 | name string
151 | fields fields
152 | args args
153 | wantErr bool
154 | }{
155 | {
156 | name: "default",
157 | fields: fields{
158 | CacheOpt: fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME")),
159 | ThrottleDuration: 5,
160 | GraceDuration: 0,
161 | },
162 | args: args{
163 | errObj: errors.New("test_throttling"),
164 | },
165 | wantErr: false,
166 | },
167 | {
168 | name: "test_error",
169 | fields: fields{
170 | CacheOpt: "/no_permission_dir",
171 | ThrottleDuration: 5,
172 | GraceDuration: 0,
173 | },
174 | args: args{
175 | errObj: errors.New("test_throttling"),
176 | },
177 | wantErr: true,
178 | },
179 | }
180 | for _, tt := range tests {
181 |
182 | t.Run(tt.name, func(t *testing.T) {
183 | th := &Throttler{
184 | CacheOpt: tt.fields.CacheOpt,
185 | ThrottleDuration: tt.fields.ThrottleDuration,
186 | }
187 |
188 | if err := th.ThrottleError(tt.args.errObj); (err != nil) != tt.wantErr {
189 | t.Errorf("Throttler.ThrottleError() error = %v, wantErr %v", err, tt.wantErr)
190 | }
191 | if tt.name == "default" && !th.IsThrottledOrGraced(tt.args.errObj) {
192 | t.Errorf("Throttler.ThrottleError() error = %v, wantErr %v", errors.New("throttling failed"), tt.wantErr)
193 | }
194 | if !tt.wantErr {
195 | err := th.CleanThrottlingCache()
196 | if err != nil {
197 | t.Errorf("Cannot clean after test. %+v", err)
198 | }
199 | }
200 |
201 | })
202 | }
203 | }
204 |
205 | func TestThrottler_getDiskCache(t *testing.T) {
206 | type fields struct {
207 | CacheOpt string
208 | ThrottleDuration int
209 | }
210 | cachePart := fmt.Sprintf("/tmp/cache/%v_throttler_disk_cache", os.Getenv("APP_NAME"))
211 | opts := diskache.Opts{
212 | Directory: cachePart,
213 | }
214 | dc, err := diskache.New(&opts)
215 | if err != nil {
216 | t.Errorf("Throttler.getDiskCache() error = %v", err)
217 | return
218 | }
219 |
220 | tests := []struct {
221 | name string
222 | fields fields
223 | want *diskache.Diskache
224 | wantErr bool
225 | }{
226 | {
227 | name: "TestThrottler_getDiskCache_success",
228 | fields: fields{
229 | CacheOpt: cachePart,
230 | ThrottleDuration: 5,
231 | },
232 | want: dc,
233 | wantErr: false,
234 | },
235 | }
236 | for _, tt := range tests {
237 | t.Run(tt.name, func(t *testing.T) {
238 | th := &Throttler{
239 | CacheOpt: tt.fields.CacheOpt,
240 | ThrottleDuration: tt.fields.ThrottleDuration,
241 | }
242 | got, err := th.getDiskCache()
243 | if (err != nil) != tt.wantErr {
244 | t.Errorf("Throttler.getDiskCache() error = %v, wantErr %v", err, tt.wantErr)
245 | return
246 | }
247 | if !reflect.DeepEqual(got, tt.want) {
248 | t.Errorf("Throttler.getDiskCache() = %v, want %v", got, tt.want)
249 | }
250 | })
251 | }
252 | }
253 |
254 | func Test_isOverThrottleDuration(t *testing.T) {
255 | type args struct {
256 | cachedTime string
257 | throttleDuration int
258 | graceDuration int
259 | }
260 | tests := []struct {
261 | name string
262 | args args
263 | want bool
264 | }{
265 | {
266 | name: "Test_isOverThrottleDuration_true",
267 | args: args{
268 | cachedTime: time.Now().Add(-3 * time.Minute).Format(time.RFC3339), // -3 minutes => pass 2 minutes durations
269 | throttleDuration: 2,
270 | graceDuration: 0,
271 | },
272 | want: true,
273 | },
274 | {
275 | name: "Test_isOverThrottleDuration_false",
276 | args: args{
277 | cachedTime: time.Now().Add(1 * time.Minute).Format(time.RFC3339), // 1 minute ahead of current < throtte duration 2
278 | throttleDuration: 2,
279 | graceDuration: 0,
280 | },
281 | want: false,
282 | },
283 | }
284 | for _, tt := range tests {
285 | t.Run(tt.name, func(t *testing.T) {
286 | if got := isOverThrottleDuration(tt.args.cachedTime, tt.args.throttleDuration); got != tt.want {
287 | t.Errorf("isOverThrottleDuration() = %v, want %v", got, tt.want)
288 | }
289 | })
290 | }
291 | }
292 |
293 | func Test_isOverGraceDuration(t *testing.T) {
294 | type args struct {
295 | cachedTime string
296 | throttleDuration int
297 | graceDuration int
298 | }
299 | tests := []struct {
300 | name string
301 | args args
302 | want bool
303 | }{
304 | {
305 | name: "Test_isOverGraceDuration_true",
306 | args: args{
307 | cachedTime: time.Now().Add(-5 * time.Second).Format(time.RFC3339), // 2 sec after grace duration is over
308 | throttleDuration: 0,
309 | graceDuration: 3,
310 | },
311 | want: true,
312 | },
313 | {
314 | name: "Test_isOverGraceDuration_false",
315 | args: args{
316 | cachedTime: time.Now().Add(2 * time.Second).Format(time.RFC3339), // still 8 sec left for grace duration
317 | throttleDuration: 0,
318 | graceDuration: 10,
319 | },
320 | want: false,
321 | },
322 | }
323 | for _, tt := range tests {
324 | t.Run(tt.name, func(t *testing.T) {
325 | if got := isOverGraceDuration(tt.args.cachedTime, tt.args.graceDuration); got != tt.want {
326 | t.Errorf("isOverGraceDuration() = %v, want %v", got, tt.want)
327 | }
328 | })
329 | }
330 |
331 | }
332 |
--------------------------------------------------------------------------------