Compare commits
3 commits
test
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
6b779b1721 | |||
3517d64da8 | |||
b5f063381e |
8 changed files with 272 additions and 108 deletions
2
.github/workflows/.github-ci.yml
vendored
2
.github/workflows/.github-ci.yml
vendored
|
@ -46,5 +46,5 @@ jobs:
|
|||
push: true
|
||||
allow: network.host
|
||||
network: host
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.docker.outputs.tags }}
|
|
@ -1,11 +1,11 @@
|
|||
FROM golang:1.17-alpine AS build-env
|
||||
FROM golang:1.24.1-alpine AS build-env
|
||||
ENV GO111MODULE=on
|
||||
WORKDIR /go/src/github.com/hugomd/ascii-live/
|
||||
RUN apk add ca-certificates
|
||||
COPY . /go/src/github.com/hugomd/ascii-live/
|
||||
RUN cd /go/src/github.com/hugomd/ascii-live && \
|
||||
go mod download && \
|
||||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
|
||||
CGO_ENABLED=0 GOOS=linux go build -installsuffix cgo -o main .
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build-env /go/src/github.com/hugomd/ascii-live/main /
|
||||
|
|
15
go.mod
15
go.mod
|
@ -1,8 +1,15 @@
|
|||
module github.com/hugomd/ascii-live
|
||||
module ascii-live
|
||||
|
||||
go 1.17
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/golang/glog v1.2.4
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
20
go.sum
20
go.sum
|
@ -1,4 +1,16 @@
|
|||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
|
||||
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
103
handlers/handlers.go
Normal file
103
handlers/handlers.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ascii-live/frames"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var NotFoundMessage = map[string]string{
|
||||
"error": "Frames not found. Navigate to /ascii/list for list of frames. Navigate to https://github.com/hugomd/ascii-live to submit new frames.",
|
||||
}
|
||||
|
||||
var NotCurledMessage = map[string]string{
|
||||
"error": "You almost ruined a good surprise. Come on, curl it in terminal.",
|
||||
}
|
||||
|
||||
var availableFrames []string
|
||||
|
||||
func init() {
|
||||
for k := range frames.FrameMap {
|
||||
availableFrames = append(availableFrames, k)
|
||||
}
|
||||
}
|
||||
|
||||
func writeJson(w http.ResponseWriter, r *http.Request, res interface{}, status int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.WriteHeader(status)
|
||||
fmt.Fprint(w, string(data))
|
||||
}
|
||||
|
||||
func ListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJson(w, r, map[string][]string{"frames": availableFrames}, http.StatusOK)
|
||||
}
|
||||
|
||||
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJson(w, r, NotFoundMessage, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func NotCurledHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJson(w, r, NotCurledMessage, http.StatusExpectationFailed)
|
||||
}
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
flusher := w.(http.Flusher)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
frameSource := vars["frameSource"]
|
||||
glog.Infof("Frame source %s", frameSource)
|
||||
|
||||
frames, ok := frames.FrameMap[frameSource]
|
||||
if !ok {
|
||||
NotFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
userAgent := r.Header.Get("User-Agent")
|
||||
if !strings.Contains(userAgent, "curl") {
|
||||
NotCurledHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
i := 0
|
||||
for {
|
||||
select {
|
||||
// Handle client disconnects
|
||||
case <-ctx.Done():
|
||||
glog.Infof("Client stopped listening")
|
||||
return
|
||||
default:
|
||||
if i >= frames.GetLength() {
|
||||
i = 0
|
||||
}
|
||||
// Artificially wait between reponses.
|
||||
time.Sleep(frames.GetSleep())
|
||||
|
||||
// Clear screen
|
||||
clearScreen := "\033[2J\033[H"
|
||||
newLine := "\n"
|
||||
|
||||
// Write frames
|
||||
fmt.Fprint(w, clearScreen+frames.GetFrame(i)+newLine)
|
||||
i++
|
||||
|
||||
// Send some data.
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
}
|
101
main.go
101
main.go
|
@ -1,108 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hugomd/ascii-live/frames"
|
||||
"ascii-live/handlers"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var NotFoundMessage = map[string]string{
|
||||
"error": "Frames not found. Navigate to /list for list of frames. Navigate to https://github.com/hugomd/ascii-live to submit new frames.",
|
||||
}
|
||||
|
||||
var NotCurledMessage = map[string]string{
|
||||
"error": "You almost ruined a good surprise. Come on, curl it in terminal.",
|
||||
}
|
||||
|
||||
var availableFrames []string
|
||||
|
||||
func init() {
|
||||
for k := range frames.FrameMap {
|
||||
availableFrames = append(availableFrames, k)
|
||||
}
|
||||
}
|
||||
|
||||
func writeJson(w http.ResponseWriter, r *http.Request, res interface{}, status int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.WriteHeader(status)
|
||||
fmt.Fprint(w, string(data))
|
||||
}
|
||||
|
||||
func listHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJson(w, r, map[string][]string{"frames": availableFrames}, http.StatusOK)
|
||||
}
|
||||
|
||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJson(w, r, NotFoundMessage, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func notCurledHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJson(w, r, NotCurledMessage, http.StatusExpectationFailed)
|
||||
}
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
cn := w.(http.CloseNotifier)
|
||||
flusher := w.(http.Flusher)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
frameSource := vars["frameSource"]
|
||||
glog.Infof("Frame source %s", frameSource)
|
||||
|
||||
frames, ok := frames.FrameMap[frameSource]
|
||||
if !ok {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
userAgent := r.Header.Get("User-Agent")
|
||||
if !strings.Contains(userAgent, "curl") {
|
||||
notCurledHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
i := 0
|
||||
for {
|
||||
select {
|
||||
// Handle client disconnects
|
||||
case <-cn.CloseNotify():
|
||||
glog.Infof("Client stopped listening")
|
||||
return
|
||||
default:
|
||||
if i >= frames.GetLength() {
|
||||
i = 0
|
||||
}
|
||||
// Artificially wait between reponses.
|
||||
time.Sleep(frames.GetSleep())
|
||||
|
||||
// Clear screen
|
||||
clearScreen := "\033[2J\033[H"
|
||||
newLine := "\n"
|
||||
|
||||
// Write frames
|
||||
fmt.Fprint(w, clearScreen+frames.GetFrame(i)+newLine)
|
||||
i++
|
||||
|
||||
// Send some data.
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Server.
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
@ -110,9 +17,9 @@ func main() {
|
|||
flag.Set("logtostderr", "true")
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/ascii-live/list", listHandler).Methods("GET")
|
||||
r.HandleFunc("/ascii-live/{frameSource}", handler).Methods("GET")
|
||||
r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||
r.HandleFunc("/ascii-live/list", handlers.ListHandler).Methods("GET")
|
||||
r.HandleFunc("/ascii-live/{frameSource}", handlers.Handler).Methods("GET")
|
||||
r.NotFoundHandler = http.HandlerFunc(handlers.NotFoundHandler)
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: r,
|
||||
|
|
69
tests/handlers_test.go
Normal file
69
tests/handlers_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"ascii-live/handlers"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func TestListHandler(t *testing.T) {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/ascii-live/list", handlers.ListHandler)
|
||||
|
||||
req, err := http.NewRequest("GET", "/ascii-live/list", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
r.ServeHTTP(recorder, req)
|
||||
|
||||
if status := recorder.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
var response map[string][]string
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
|
||||
t.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotFoundHandler(t *testing.T) {
|
||||
r := mux.NewRouter()
|
||||
r.NotFoundHandler = http.HandlerFunc(handlers.NotFoundHandler)
|
||||
|
||||
req, err := http.NewRequest("GET", "/not-existing", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
r.ServeHTTP(recorder, req) // Router verarbeitet die Anfrage
|
||||
|
||||
if status := recorder.Code; status != http.StatusNotFound {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotCurledHandler(t *testing.T) {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/ascii-live/donut", handlers.NotCurledHandler)
|
||||
|
||||
req, err := http.NewRequest("GET", "/ascii-live/donut", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36")
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
r.ServeHTTP(recorder, req)
|
||||
|
||||
if status := recorder.Code; status != http.StatusExpectationFailed {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusExpectationFailed)
|
||||
}
|
||||
}
|
66
tests/handlers_testify_test.go
Normal file
66
tests/handlers_testify_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"ascii-live/handlers"
|
||||
)
|
||||
|
||||
func TestListHandler2(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/ascii/list", nil)
|
||||
require.NoError(t, err, "Failed to create request")
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(handlers.ListHandler)
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, recorder.Code, "Status code should be OK")
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("Content-Type"), "Content-Type should be application/json")
|
||||
|
||||
var responseBody map[string][]string
|
||||
err = json.Unmarshal(recorder.Body.Bytes(), &responseBody)
|
||||
require.NoError(t, err, "Failed to unmarshal response JSON")
|
||||
|
||||
}
|
||||
|
||||
func TestNotFoundHandler2(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/some/unknown/path", nil)
|
||||
require.NoError(t, err, "Failed to create request")
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(handlers.NotFoundHandler) // Test directly
|
||||
|
||||
expectedBodyBytes, err := json.Marshal(handlers.NotFoundMessage)
|
||||
require.NoError(t, err, "Failed to marshal expected NotFoundMessage")
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, recorder.Code, "Status code should be Not Found")
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("Content-Type"), "Content-Type should be application/json")
|
||||
assert.JSONEq(t, string(expectedBodyBytes), recorder.Body.String(), "Response body does not match expected NotFoundMessage")
|
||||
}
|
||||
|
||||
func TestNotCurledHandler2(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/ascii/someframe", nil)
|
||||
require.NoError(t, err, "Failed to create request")
|
||||
// crucially, DO NOT set User-Agent to 'curl'
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(handlers.NotCurledHandler) // Test directly
|
||||
|
||||
expectedBodyBytes, err := json.Marshal(handlers.NotCurledMessage)
|
||||
require.NoError(t, err, "Failed to marshal expected NotCurledMessage")
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, http.StatusExpectationFailed, recorder.Code, "Status code should be Expectation Failed")
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("Content-Type"), "Content-Type should be application/json")
|
||||
assert.JSONEq(t, string(expectedBodyBytes), recorder.Body.String(), "Response body does not match expected NotCurledMessage")
|
||||
}
|
Loading…
Reference in a new issue