Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Financial-Times/content-unroller …
Browse files Browse the repository at this point in the history
…into fix/CPH-97-alpineupdate
  • Loading branch information
dbelev committed Jul 22, 2019
2 parents 65d8934 + c49c8ab commit 98f627f
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 333 deletions.
4 changes: 3 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ jobs:
command: go build -v
- run:
name: Run tests
command: go test -race -cover -coverprofile=${CIRCLE_COVERAGE_REPORT}/coverage.out ./... | go-junit-report > ${CIRCLE_TEST_REPORTS}/junit.xml
command: |
go test -v -tags=read -race -cover -coverprofile=${CIRCLE_COVERAGE_REPORT}/coverage.out ./... | go-junit-report > ${CIRCLE_TEST_REPORTS}/junit.xml
FLOW=preview go test -v -tags=preview -race
- run:
name: Upload coverage
command: goveralls -coverprofile=${CIRCLE_COVERAGE_REPORT}/coverage.out -service=circle-ci -repotoken=${COVERALLS_TOKEN}
Expand Down
4 changes: 4 additions & 0 deletions content/applogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ func (appLogger *appLogger) Info(tid string, uuid string, message string) {
func (appLogger *appLogger) Errorf(tid string, format string, args ...interface{}) {
appLogger.log.WithFields(logrus.Fields{"tid": tid}).Errorf(format, args...)
}

func (appLogger *appLogger) Warnf(tid string, uuid string, format string, args ...interface{}) {
appLogger.log.WithFields(logrus.Fields{"tid": tid, "uuid": uuid}).Warnf(format, args...)
}
7 changes: 6 additions & 1 deletion content/healthchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ func (sc *ServiceConfig) GtgCheck() gtg.Status {
}
return gtg.Status{GoodToGo: true}
}
return gtg.FailFastParallelCheck([]gtg.StatusChecker{
contentStoreCheck,
})()
}

func (sc *ServiceConfig) GtgCheckPreview() gtg.Status {
contentPreviewCheck := func() gtg.Status {
msg, err := sc.checkServiceAvailability(sc.ContentPreviewAppName, sc.ContentPreviewAppHealthURI)
if err != nil {
Expand All @@ -34,7 +40,6 @@ func (sc *ServiceConfig) GtgCheck() gtg.Status {
return gtg.Status{GoodToGo: true}
}
return gtg.FailFastParallelCheck([]gtg.StatusChecker{
contentStoreCheck,
contentPreviewCheck,
})()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Values used for the deployed application.
replicaCount: 2
service:
name: content-preview-unroller

content_store:
app_name: content-public-read
host: http://content-public-read:8080

content_preview:
app_name: content-public-read-preview
host: http://content-public-read-preview:8080

content_path: /content
internal_content_path: /internalcontent

flow: preview
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ content_store:
content_preview:
app_name: content-public-read-preview
host: http://content-public-read-preview:8080

content_path: /content
internal_content_path: /internalcontent
internal_content_path: /internalcontent

flow: read
2 changes: 2 additions & 0 deletions helm/content-unroller/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ spec:
value: {{ .Values.content_path }}
- name: INTERNAL_CONTENT_PATH
value: {{ .Values.internal_content_path }}
- name: FLOW
value: {{ .Values.flow }}
- name: API_HOST
valueFrom:
configMapKeyRef:
Expand Down
39 changes: 31 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ func main() {
Desc: "API host to use for URLs in responses",
EnvVar: "API_HOST",
})
flow := app.String(cli.StringOpt{
Name: "flow",
Value: "read",
Desc: "Marks if it is 'read' or 'preview' flow (default: read)",
EnvVar: "FLOW",
})

app.Action = func() {
httpClient := &http.Client{
Expand Down Expand Up @@ -105,7 +111,14 @@ func main() {
reader := content.NewContentReader(readerConfig, httpClient)
unroller := content.NewContentUnroller(reader, *apiHost)

h := setupServiceHandler(unroller, sc)
switch *flow {
case "read", "preview":
log.Infof("Value of 'flow' set to '%s'.", *flow)
default:
log.Warnf("Value of 'flow' should be one of: 'read' or 'preview', defaulting to 'read'.")
}

h := setupServiceHandler(unroller, sc, *flow)
err := http.ListenAndServe(":"+*port, h)
if err != nil {
log.Fatalf("Unable to start server: %v", err)
Expand All @@ -117,25 +130,35 @@ func main() {
app.Run(os.Args)
}

func setupServiceHandler(s content.Unroller, sc content.ServiceConfig) *mux.Router {
func setupServiceHandler(s content.Unroller, sc content.ServiceConfig, flow string) *mux.Router {
r := mux.NewRouter()
ch := &content.Handler{Service: s}
r.HandleFunc("/content", ch.GetContent).Methods("POST")
r.HandleFunc("/content-preview", ch.GetContentPreview).Methods("POST")
r.HandleFunc("/internalcontent", ch.GetInternalContent).Methods("POST")
r.HandleFunc("/internalcontent-preview", ch.GetInternalContentPreview).Methods("POST")
// Splitting the read and preview flow: endpoints and healthchecks assigned accordingly
var checks []fthealth.Check
var gtgHandler func(http.ResponseWriter, *http.Request)

if flow == "preview" {
r.HandleFunc("/content-preview", ch.GetContentPreview).Methods("POST")
r.HandleFunc("/internalcontent-preview", ch.GetInternalContentPreview).Methods("POST")
checks = []fthealth.Check{sc.ContentStoreCheck(), sc.ContentPreviewCheck()}
gtgHandler = httphandlers.NewGoodToGoHandler(gtg.StatusChecker(sc.GtgCheckPreview))
} else {
// the default value for flow is "read"
r.HandleFunc("/content", ch.GetContent).Methods("POST")
r.HandleFunc("/internalcontent", ch.GetInternalContent).Methods("POST")
checks = []fthealth.Check{sc.ContentStoreCheck()}
gtgHandler = httphandlers.NewGoodToGoHandler(gtg.StatusChecker(sc.GtgCheck))
}

r.Path(httphandlers.BuildInfoPath).HandlerFunc(httphandlers.BuildInfoHandler)
r.Path(httphandlers.PingPath).HandlerFunc(httphandlers.PingHandler)

checks := []fthealth.Check{sc.ContentStoreCheck(), sc.ContentPreviewCheck()}
hc := fthealth.TimedHealthCheck{
HealthCheck: fthealth.HealthCheck{SystemCode: AppCode, Name: AppName, Description: AppDesc, Checks: checks},
Timeout: 10 * time.Second,
}

r.Path("/__health").Handler(handlers.MethodHandler{"GET": http.HandlerFunc(fthealth.Handler(&hc))})
gtgHandler := httphandlers.NewGoodToGoHandler(gtg.StatusChecker(sc.GtgCheck))
r.Path("/__gtg").Handler(handlers.MethodHandler{"GET": http.HandlerFunc(gtgHandler)})
return r
}
Expand Down
188 changes: 188 additions & 0 deletions main_preview_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// +build preview

package main

import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

var flow = "preview"

func TestShouldReturn200ForContentPreview(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-content-valid-response.json", false)
contentPreviewServiceMock := startContentServerMock("test-resources/source-contentpreview-valid-response.json", true)
startUnrollerService(contentStoreServiceMock.URL, contentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer contentPreviewServiceMock.Close()
defer unrollerService.Close()

expected, err := ioutil.ReadFile("test-resources/contentpreview-valid-response.json")
assert.NoError(t, err, "")

body, err := ioutil.ReadFile("test-resources/content-valid-request.json")
assert.NoError(t, err, "Cannot read file necessary for test case")
resp, err := http.Post(unrollerService.URL+"/content-preview", "application/json", bytes.NewReader(body))
assert.NoError(t, err, "Should not fail")
defer resp.Body.Close()
actualResponse, err := ioutil.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.NoError(t, err, "")
assert.JSONEq(t, string(expected), string(actualResponse))
}

func TestContentPreview_ShouldReturn400InvalidJson(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-content-valid-response.json", false)
contentPreviewServiceMock := startContentServerMock("test-resources/source-contentpreview-valid-response.json", true)
startUnrollerService(contentStoreServiceMock.URL, contentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer contentPreviewServiceMock.Close()
defer unrollerService.Close()

body := `{"body":"invalid""body"}`
resp, err := http.Post(unrollerService.URL+"/content-preview", "application/json", strings.NewReader(body))
assert.NoError(t, err, "")
defer resp.Body.Close()

assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestContentPreview_ShouldReturn400InvalidArticle(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-content-valid-response.json", false)
contentPreviewServiceMock := startContentServerMock("test-resources/source-contentpreview-valid-response.json", true)
startUnrollerService(contentStoreServiceMock.URL, contentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer contentPreviewServiceMock.Close()
defer unrollerService.Close()

body := `{"id":"36037ab1-da3b-35bf-b5ee-4fc23723b635"}`
resp, err := http.Post(unrollerService.URL+"/content-preview", "application/json", strings.NewReader(body))
assert.NoError(t, err, "")
defer resp.Body.Close()

assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestShouldReturn200ForInternalContentPreview(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-internalcontent-valid-lead-images-reasponse.json", false)
internalContentPreviewServiceMock := startContentServerMock("test-resources/source-internalcontentpreview-valid-response.json", true)
startUnrollerService(contentStoreServiceMock.URL, internalContentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer internalContentPreviewServiceMock.Close()
defer unrollerService.Close()

expected, err := ioutil.ReadFile("test-resources/internalcontentpreview-valid-response.json")
assert.NoError(t, err, "")

body, err := ioutil.ReadFile("test-resources/internalcontentpreview-valid-request.json")
assert.NoError(t, err, "Cannot read file necessary for test case")
resp, err := http.Post(unrollerService.URL+"/internalcontent-preview", "application/json", bytes.NewReader(body))
assert.NoError(t, err, "Should not fail")
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode)
actualResponse, err := ioutil.ReadAll(resp.Body)
assert.NoError(t, err, "")
assert.JSONEq(t, string(expected), string(actualResponse))
}

func TestInternalContentPreview_ShouldReturn400InvalidJson(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-internalcontent-valid-lead-images-reasponse.json", false)
internalContentPreviewServiceMock := startContentServerMock("test-resources/source-internalcontentpreview-valid-response.json", true)
startUnrollerService(contentStoreServiceMock.URL, internalContentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer internalContentPreviewServiceMock.Close()
defer unrollerService.Close()

body := `{"body":"invalid""body"}`
resp, err := http.Post(unrollerService.URL+"/internalcontent-preview", "application/json", strings.NewReader(body))
assert.NoError(t, err, "")
defer resp.Body.Close()

assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestInternalContentPreview_ShouldReturn400InvalidArticle(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-internalcontent-valid-lead-images-reasponse.json", false)
internalContentPreviewServiceMock := startContentServerMock("test-resources/source-internalcontentpreview-valid-response.json", true)
startUnrollerService(contentStoreServiceMock.URL, internalContentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer internalContentPreviewServiceMock.Close()
defer unrollerService.Close()

body := `{"id":"36037ab1-da3b-35bf-b5ee-4fc23723b635"}`
resp, err := http.Post(unrollerService.URL+"/internalcontent-preview", "application/json", strings.NewReader(body))
assert.NoError(t, err, "")
defer resp.Body.Close()

assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestShouldNotBeHealthyWhenContentStoreIsNotHappy(t *testing.T) {
contentStoreServiceMock := startUnhealthyContentServerMock()
contentPreviewServiceMock := startContentServerMock("test-resources/source-contentpreview-valid-response.json", true)
startUnrollerService(contentStoreServiceMock.URL, contentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer contentPreviewServiceMock.Close()
defer unrollerService.Close()

resp, err := http.Get(unrollerService.URL + "/__health")

assert.NoError(t, err, "Cannot send request to gtg endpoint")
assert.Equal(t, http.StatusOK, resp.StatusCode, "Response status should be 503")

respBody, err := ioutil.ReadAll(resp.Body)

assert.NoError(t, err, "")
assertMsg := fmt.Sprintf(`"id":"check-connect-%v","name":"Check connectivity to %v","ok":false`, contentStoreAppName, contentStoreAppName)
assert.Contains(t, string(respBody), assertMsg)
}

func TestShouldNotBeHealthyWhenContentPreviewIsNotHappy(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-content-valid-response.json", false)
contentPreviewServiceMock := startUnhealthyContentServerMock()
startUnrollerService(contentStoreServiceMock.URL, contentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer contentPreviewServiceMock.Close()
defer unrollerService.Close()

resp, err := http.Get(unrollerService.URL + "/__health")

assert.NoError(t, err, "Cannot send request to gtg endpoint")
assert.Equal(t, http.StatusOK, resp.StatusCode, "Response status should be 503")

respBody, err := ioutil.ReadAll(resp.Body)

assert.NoError(t, err, "")
assertMsg := fmt.Sprintf(`"id":"check-connect-%v","name":"Check connectivity to %v","ok":false`, contentPreviewAppName, contentPreviewAppName)
assert.Contains(t, string(respBody), assertMsg)
}

func TestShouldNotBeGoodToGoWhenContentPreviewIsNotHappy(t *testing.T) {
contentStoreServiceMock := startContentServerMock("test-resources/source-content-valid-response.json", false)
contentPreviewServiceMock := startUnhealthyContentServerMock()
startUnrollerService(contentStoreServiceMock.URL, contentPreviewServiceMock.URL, flow)

defer contentStoreServiceMock.Close()
defer contentPreviewServiceMock.Close()
defer unrollerService.Close()

resp, err := http.Get(unrollerService.URL + "/__gtg")
assert.NoError(t, err, "Cannot send request to gtg endpoint")
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode, "Response status should be 503")
}
Loading

0 comments on commit 98f627f

Please sign in to comment.