Adding simple visits handler to send a notification on site load with some simple rate limiting and caching
This commit is contained in:
parent
57a3bc4ecd
commit
f032c25ef7
6 changed files with 400 additions and 52 deletions
|
|
@ -16,8 +16,9 @@ import (
|
||||||
var token string
|
var token string
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
ntfy *Ntfy
|
ntfy *Ntfy
|
||||||
|
visits *VisitHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStaticFile(relPath string, contentType string, w http.ResponseWriter) {
|
func getStaticFile(relPath string, contentType string, w http.ResponseWriter) {
|
||||||
|
|
@ -153,6 +154,7 @@ func SetupAuth() {
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Printf("%s - [%s] (%s) %s\n", time.Now().Format(time.RFC3339), r.RemoteAddr, r.Method, r.URL)
|
fmt.Printf("%s - [%s] (%s) %s\n", time.Now().Format(time.RFC3339), r.RemoteAddr, r.Method, r.URL)
|
||||||
|
h.visits.HandleVisit(r.RemoteAddr)
|
||||||
|
|
||||||
switch true {
|
switch true {
|
||||||
case r.Method == "GET" && r.URL.Path == "/favicon.ico":
|
case r.Method == "GET" && r.URL.Path == "/favicon.ico":
|
||||||
|
|
|
||||||
4
main.go
4
main.go
|
|
@ -16,8 +16,10 @@ func main() {
|
||||||
db := SetupDatabase()
|
db := SetupDatabase()
|
||||||
SetupRsvpsTable(db)
|
SetupRsvpsTable(db)
|
||||||
|
|
||||||
|
visits := SetupVisitHandler(&VisitHandlerArgs{baseUrl: "http://ip-api.com", rateLimitAmount: 45, rateLimitDurationSeconds: 60})
|
||||||
|
|
||||||
SetupAuth()
|
SetupAuth()
|
||||||
hnd := &Handler{db, ntfy}
|
hnd := &Handler{db, ntfy, visits}
|
||||||
fmt.Println("handler ready")
|
fmt.Println("handler ready")
|
||||||
|
|
||||||
http.ListenAndServe(":8000", hnd)
|
http.ListenAndServe(":8000", hnd)
|
||||||
|
|
|
||||||
120
ntfy.go
120
ntfy.go
|
|
@ -71,46 +71,6 @@ func SetupNtfyClient() *Ntfy {
|
||||||
return &Ntfy{client: tp}
|
return &Ntfy{client: tp}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTitle(rsvp *Rsvp) string {
|
|
||||||
builder := new(strings.Builder)
|
|
||||||
if rsvp.Attending {
|
|
||||||
peoplePerson := "people"
|
|
||||||
if rsvp.PartySize == 1 {
|
|
||||||
peoplePerson = "person"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(builder, "%d %s confirmed!", rsvp.PartySize, peoplePerson)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(builder, "Someone can't make it")
|
|
||||||
}
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildMessage(rsvp *Rsvp) string {
|
|
||||||
builder := new(strings.Builder)
|
|
||||||
if rsvp.Attending {
|
|
||||||
builder.WriteString("Here's who is coming 👇")
|
|
||||||
for _, mem := range rsvp.PartyMembers {
|
|
||||||
age := "adult"
|
|
||||||
if mem.Child {
|
|
||||||
age = "👶"
|
|
||||||
} else {
|
|
||||||
age = "🧓"
|
|
||||||
}
|
|
||||||
dp := "n/a"
|
|
||||||
if len(mem.DietaryPreferences) > 0 {
|
|
||||||
dp = mem.DietaryPreferences
|
|
||||||
}
|
|
||||||
fmt.Fprintf(builder, "\n- %s %s, %s", age, mem.Name, dp)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, mem := range rsvp.PartyMembers {
|
|
||||||
fmt.Fprintf(builder, "%s\n", mem.Name)
|
|
||||||
}
|
|
||||||
builder.WriteString("can't make it")
|
|
||||||
}
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func numberToEmoji(num int64) string {
|
func numberToEmoji(num int64) string {
|
||||||
switch num {
|
switch num {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
@ -138,7 +98,47 @@ func numberToEmoji(num int64) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTags(rsvp *Rsvp) []string {
|
func buildRsvpTitle(rsvp *Rsvp) string {
|
||||||
|
builder := new(strings.Builder)
|
||||||
|
if rsvp.Attending {
|
||||||
|
peoplePerson := "people"
|
||||||
|
if rsvp.PartySize == 1 {
|
||||||
|
peoplePerson = "person"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(builder, "%d %s confirmed!", rsvp.PartySize, peoplePerson)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(builder, "Someone can't make it")
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRsvpMessage(rsvp *Rsvp) string {
|
||||||
|
builder := new(strings.Builder)
|
||||||
|
if rsvp.Attending {
|
||||||
|
builder.WriteString("Here's who is coming 👇")
|
||||||
|
for _, mem := range rsvp.PartyMembers {
|
||||||
|
age := "adult"
|
||||||
|
if mem.Child {
|
||||||
|
age = "👶"
|
||||||
|
} else {
|
||||||
|
age = "🧓"
|
||||||
|
}
|
||||||
|
dp := "n/a"
|
||||||
|
if len(mem.DietaryPreferences) > 0 {
|
||||||
|
dp = mem.DietaryPreferences
|
||||||
|
}
|
||||||
|
fmt.Fprintf(builder, "\n- %s %s, %s", age, mem.Name, dp)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, mem := range rsvp.PartyMembers {
|
||||||
|
fmt.Fprintf(builder, "%s\n", mem.Name)
|
||||||
|
}
|
||||||
|
builder.WriteString("can't make it")
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRsvpTags(rsvp *Rsvp) []string {
|
||||||
if rsvp.Attending {
|
if rsvp.Attending {
|
||||||
return []string{"white_check_mark", numberToEmoji(rsvp.PartySize)}
|
return []string{"white_check_mark", numberToEmoji(rsvp.PartySize)}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -146,18 +146,46 @@ func buildTags(rsvp *Rsvp) []string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildNtfyMessage(topic string, rsvp *Rsvp) *gotfy.Message {
|
func BuildRsvpMessage(topic string, rsvp *Rsvp) *gotfy.Message {
|
||||||
return &gotfy.Message{
|
return &gotfy.Message{
|
||||||
Topic: topic,
|
Topic: topic,
|
||||||
Message: buildMessage(rsvp),
|
Message: buildRsvpMessage(rsvp),
|
||||||
Title: buildTitle(rsvp),
|
Title: buildRsvpTitle(rsvp),
|
||||||
Tags: buildTags(rsvp),
|
Tags: buildRsvpTags(rsvp),
|
||||||
Priority: gotfy.Default,
|
Priority: gotfy.Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Ntfy) PublishNewRsvpNotification(rsvp *Rsvp) (string, error) {
|
func (n *Ntfy) PublishNewRsvpNotification(rsvp *Rsvp) (string, error) {
|
||||||
resp, err := n.client.SendMessage(context.Background(), BuildNtfyMessage("collinenlucy_nl", rsvp))
|
resp, err := n.client.SendMessage(context.Background(), BuildRsvpMessage("collinenlucy_nl", rsvp))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildVisitTitle(visit *Visit) string {
|
||||||
|
return "Someone visited!"
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildVisitMessage(visit *Visit) string {
|
||||||
|
return fmt.Sprintf("Got a visit from %s (%s)", visit.Location, visit.RemoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildVisitMessage(topic string, visit *Visit) *gotfy.Message {
|
||||||
|
return &gotfy.Message{
|
||||||
|
Topic: topic,
|
||||||
|
Message: buildVisitMessage(visit),
|
||||||
|
Title: buildVisitTitle(visit),
|
||||||
|
Tags: []string{"eyes"},
|
||||||
|
Priority: gotfy.Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Ntfy) PublishNewVisitNotification(visit *Visit) (string, error) {
|
||||||
|
resp, err := n.client.SendMessage(context.Background(), BuildVisitMessage("collinenlucy_nl", visit))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ func TestBuildNtfyMessage(t *testing.T) {
|
||||||
{Name: "test", Child: false, DietaryPreferences: ""},
|
{Name: "test", Child: false, DietaryPreferences: ""},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
msg := BuildNtfyMessage("test", rsvp)
|
msg := BuildRsvpMessage("test", rsvp)
|
||||||
|
|
||||||
if msg.Topic != "test" {
|
if msg.Topic != "test" {
|
||||||
t.Fatal("message topic is incorrect")
|
t.Fatal("message topic is incorrect")
|
||||||
|
|
@ -34,7 +34,7 @@ func TestBuildNtfyMessage(t *testing.T) {
|
||||||
{Name: "test", Child: false, DietaryPreferences: ""},
|
{Name: "test", Child: false, DietaryPreferences: ""},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
msg = BuildNtfyMessage("test", rsvp)
|
msg = BuildRsvpMessage("test", rsvp)
|
||||||
|
|
||||||
if msg.Topic != "test" {
|
if msg.Topic != "test" {
|
||||||
t.Fatal("message topic is incorrect")
|
t.Fatal("message topic is incorrect")
|
||||||
|
|
@ -58,7 +58,7 @@ func TestBuildNtfyMessage(t *testing.T) {
|
||||||
{Name: "test2", Child: true, DietaryPreferences: "no tobacco"},
|
{Name: "test2", Child: true, DietaryPreferences: "no tobacco"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
msg = BuildNtfyMessage("test", rsvp)
|
msg = BuildRsvpMessage("test", rsvp)
|
||||||
|
|
||||||
if msg.Topic != "test" {
|
if msg.Topic != "test" {
|
||||||
t.Fatal("message topic is incorrect")
|
t.Fatal("message topic is incorrect")
|
||||||
|
|
|
||||||
139
visits.go
Normal file
139
visits.go
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Visit struct {
|
||||||
|
RemoteAddr string
|
||||||
|
Location string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeolocationResponse struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
City string `json:"city"`
|
||||||
|
RegionName string `json:"regionName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VisitHandler struct {
|
||||||
|
geolocationApiBaseUrl string
|
||||||
|
visitsCache map[string]int64
|
||||||
|
pastRequests []int64
|
||||||
|
rateLimitAmount int
|
||||||
|
rateLimitDurationSeconds int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisitHandler) buildUrl(ipAddress string) string {
|
||||||
|
return fmt.Sprintf("%s/json/%s?fields=country,regionName,city", v.geolocationApiBaseUrl, ipAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisitHandler) limitRate() bool {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
idxToRemove := make([]int, 0)
|
||||||
|
for idx, ts := range v.pastRequests {
|
||||||
|
if now.Sub(time.Unix(ts, 0)).Seconds() >= float64(v.rateLimitDurationSeconds) {
|
||||||
|
idxToRemove = append(idxToRemove, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(idxToRemove) - 1; i >= 0; i-- {
|
||||||
|
idx := idxToRemove[i]
|
||||||
|
v.pastRequests = append(v.pastRequests[:idx], v.pastRequests[idx+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.pastRequests) == 0 {
|
||||||
|
v.pastRequests = append(v.pastRequests, now.Unix())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal := false
|
||||||
|
|
||||||
|
if len(v.pastRequests) < v.rateLimitAmount {
|
||||||
|
v.pastRequests = append(v.pastRequests, now.Unix())
|
||||||
|
retVal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisitHandler) getLocation(ipAddress string) *GeolocationResponse {
|
||||||
|
if !v.limitRate() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(v.buildUrl(ipAddress))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var geoResponse GeolocationResponse
|
||||||
|
err = json.Unmarshal(body, &geoResponse)
|
||||||
|
return &geoResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisitHandler) createVisit(ipAddress string) *Visit {
|
||||||
|
loc := v.getLocation(ipAddress)
|
||||||
|
if loc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
locationParts := []string{}
|
||||||
|
if loc.City != "" {
|
||||||
|
locationParts = append(locationParts, loc.City)
|
||||||
|
}
|
||||||
|
if loc.RegionName != "" {
|
||||||
|
locationParts = append(locationParts, loc.RegionName)
|
||||||
|
}
|
||||||
|
if loc.Country != "" {
|
||||||
|
locationParts = append(locationParts, loc.Country)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Visit{
|
||||||
|
RemoteAddr: ipAddress,
|
||||||
|
Location: strings.Join(locationParts, ", "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisitHandler) HandleVisit(remoteAddr string) *Visit {
|
||||||
|
if v.visitsCache == nil {
|
||||||
|
v.visitsCache = make(map[string]int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.visitsCache[remoteAddr] == 0 || time.Since(time.Unix(v.visitsCache[remoteAddr], 0)) > time.Duration(v.rateLimitDurationSeconds) {
|
||||||
|
visit := v.createVisit(remoteAddr)
|
||||||
|
if visit == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v.visitsCache[visit.RemoteAddr] = time.Now().Unix()
|
||||||
|
return visit
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type VisitHandlerArgs struct {
|
||||||
|
baseUrl string
|
||||||
|
rateLimitAmount int
|
||||||
|
rateLimitDurationSeconds int
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupVisitHandler(args *VisitHandlerArgs) *VisitHandler {
|
||||||
|
return &VisitHandler{
|
||||||
|
geolocationApiBaseUrl: args.baseUrl,
|
||||||
|
visitsCache: make(map[string]int64),
|
||||||
|
pastRequests: make([]int64, 0),
|
||||||
|
rateLimitAmount: args.rateLimitAmount,
|
||||||
|
rateLimitDurationSeconds: args.rateLimitDurationSeconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
177
visits_test.go
Normal file
177
visits_test.go
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleVisit_Success(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"country":"Netherlands","regionName":"Noord-Holland","city":"Amsterdam"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
v := SetupVisitHandler(&VisitHandlerArgs{baseUrl: server.URL})
|
||||||
|
|
||||||
|
visit := v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "Amsterdam, Noord-Holland, Netherlands" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleVisit_NoRegion(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"country":"Netherlands","city":"Amsterdam"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
v := SetupVisitHandler(&VisitHandlerArgs{baseUrl: server.URL})
|
||||||
|
|
||||||
|
visit := v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.Fatalf("Location is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "Amsterdam, Netherlands" {
|
||||||
|
t.Fatalf("Location is %s", visit.Location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleVisit_NoCountry(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"regionName":"Noord-Holland","city":"Amsterdam"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
v := SetupVisitHandler(&VisitHandlerArgs{baseUrl: server.URL})
|
||||||
|
|
||||||
|
visit := v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "Amsterdam, Noord-Holland" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleVisit_NoCity(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"country":"Netherlands","regionName":"Noord-Holland"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
v := SetupVisitHandler(&VisitHandlerArgs{baseUrl: server.URL})
|
||||||
|
|
||||||
|
visit := v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "Noord-Holland, Netherlands" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleVisit_Nothing(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
v := SetupVisitHandler(&VisitHandlerArgs{baseUrl: server.URL})
|
||||||
|
|
||||||
|
visit := v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleVisit_RateLimit(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"country":"Netherlands","regionName":"Noord-Holland","city":"Amsterdam"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
v := SetupVisitHandler(&VisitHandlerArgs{
|
||||||
|
baseUrl: server.URL,
|
||||||
|
rateLimitAmount: 30,
|
||||||
|
rateLimitDurationSeconds: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// pre-limit
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
visit := v.HandleVisit(fmt.Sprintf("home.collinduncan%d.com", i))
|
||||||
|
if visit == nil {
|
||||||
|
t.Fatalf("Failed before rate-limiter %d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "Amsterdam, Noord-Holland, Netherlands" {
|
||||||
|
t.Fatal("Failed before rate-limiter, location")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visit := v.HandleVisit("home.collinduncan90.com")
|
||||||
|
if visit != nil {
|
||||||
|
t.Fatal("Failed to hit rate limiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
|
visit = v.HandleVisit("home.collinduncan90.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.Fatal("Failed after rate-limiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "Amsterdam, Noord-Holland, Netherlands" {
|
||||||
|
t.Fatal("Failed after rate-limiter, location")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleVisit_Cache(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"country":"Netherlands","regionName":"Noord-Holland","city":"Amsterdam"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
v := SetupVisitHandler(&VisitHandlerArgs{baseUrl: server.URL, rateLimitDurationSeconds: 1})
|
||||||
|
|
||||||
|
visit := v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit.Location != "Amsterdam, Noord-Holland, Netherlands" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
visit = v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
|
visit = v.HandleVisit("home.collinduncan.com")
|
||||||
|
if visit == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue