Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integrity header, change to command pattern in test app #239

Merged
merged 3 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions features/fixtures/app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ FROM golang:${GO_VERSION}-alpine
RUN apk update && apk upgrade && apk add git bash build-base

ENV GOPATH /app
ENV GO111MODULE="on"

COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
COPY features /app/src/features
COPY v2 /app/src/github.com/bugsnag/bugsnag-go/v2
WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2

# Ensure subsequent steps are re-run if the GO_VERSION variable changes
Expand All @@ -20,17 +22,13 @@ RUN if [[ $(echo -e "1.11\n$GO_VERSION\n1.16" | sort -V | head -2 | tail -1) ==
go install ./...; \
fi

# Copy test scenarios
COPY ./app /app/src/test
WORKDIR /app/src/test
WORKDIR /app/src/features/fixtures/app

# Create app module - avoid locking bugsnag dep by not checking it in
# Skip on old versions of Go which pre-date modules
RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \
go mod init && go mod tidy; \
echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \
go mod tidy; \
fi
RUN go mod init && go mod tidy && \
echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod && \
go mod tidy

RUN chmod +x run.sh
CMD ["/app/src/test/run.sh"]
CMD ["/app/src/features/fixtures/app/run.sh"]
42 changes: 42 additions & 0 deletions features/fixtures/app/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
"time"
)

const DEFAULT_MAZE_ADDRESS = "http://localhost:9339"

type Command struct {
Action string `json:"action,omitempty"`
ScenarioName string `json:"scenario_name,omitempty"`
APIKey string `json:"api_key,omitempty"`
NotifyEndpoint string `json:"notify_endpoint,omitempty"`
SessionsEndpoint string `json:"sessions_endpoint,omitempty"`
UUID string `json:"uuid,omitempty"`
RunUUID string `json:"run_uuid,omitempty"`
}

func GetCommand(mazeAddress string) Command {
var command Command
mazeURL := fmt.Sprintf("%+v/command", mazeAddress)
client := http.Client{Timeout: 2 * time.Second}
res, err := client.Get(mazeURL)
if err != nil {
fmt.Printf("[Bugsnag] Error while receiving command: %+v\n", err)
return command
}

if res != nil {
err = json.NewDecoder(res.Body).Decode(&command)
res.Body.Close()
if err != nil {
fmt.Printf("[Bugsnag] Error while decoding command: %+v\n", err)
return command
}
}

return command
}
127 changes: 33 additions & 94 deletions features/fixtures/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,115 +2,54 @@ package main

import (
"context"
"flag"
"fmt"
"log"
"os"
"runtime"
"strconv"
"strings"
"time"

bugsnag "github.com/bugsnag/bugsnag-go/v2"
)

func configureBasicBugsnag(testcase string, ctx context.Context) {
config := bugsnag.Configuration{
APIKey: os.Getenv("API_KEY"),
AppVersion: os.Getenv("APP_VERSION"),
AppType: os.Getenv("APP_TYPE"),
Hostname: os.Getenv("HOSTNAME"),
MainContext: ctx,
}

if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" {
config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",")
}
var scenariosMap = map[string]func(Command) func(){}

if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" {
config.ReleaseStage = releaseStage
}

if filters := os.Getenv("PARAMS_FILTERS"); filters != "" {
config.ParamsFilters = []string{filters}
}

sync, err := strconv.ParseBool(os.Getenv("SYNCHRONOUS"))
if err == nil {
config.Synchronous = sync
}

acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS"))
if err == nil {
config.AutoCaptureSessions = acs
func main() {
addr := os.Getenv("DEFAULT_MAZE_ADDRESS")
if addr == "" {
addr = DEFAULT_MAZE_ADDRESS
}

switch testcase {
case "endpoint-notify":
config.Endpoints = bugsnag.Endpoints{Notify: os.Getenv("BUGSNAG_ENDPOINT")}
case "endpoint-session":
config.Endpoints = bugsnag.Endpoints{Sessions: os.Getenv("BUGSNAG_ENDPOINT")}
default:
config.Endpoints = bugsnag.Endpoints{
Notify: os.Getenv("BUGSNAG_ENDPOINT"),
Sessions: os.Getenv("BUGSNAG_ENDPOINT"),
}
endpoints := bugsnag.Endpoints{
Notify: fmt.Sprintf("%+v/notify", addr),
Sessions: fmt.Sprintf("%+v/sessions", addr),
}
bugsnag.Configure(config)

time.Sleep(200 * time.Millisecond)
// HAS TO RUN FIRST BECAUSE OF PANIC WRAP
// https://github.com/bugsnag/panicwrap/blob/master/panicwrap.go#L177-L203
bugsnag.Configure(bugsnag.Configuration{
APIKey: "166f5ad3590596f9aa8d601ea89af845",
Endpoints: endpoints,
})
// Increase publish rate for testing
bugsnag.DefaultSessionPublishInterval = time.Millisecond * 100
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

test := flag.String("test", "handled", "what the app should send, either handled, unhandled, session, autonotify")
flag.Parse()

configureBasicBugsnag(*test, ctx)
time.Sleep(100 * time.Millisecond) // Ensure tests are less flaky by ensuring the start-up session gets sent

switch *test {
case "unhandled":
unhandledCrash()
case "handled", "endpoint-legacy", "endpoint-notify", "endpoint-session":
handledError()
case "handled-with-callback":
handledCallbackError()
case "session":
session()
case "autonotify":
autonotify()
case "metadata":
metadata()
case "onbeforenotify":
onBeforeNotify()
case "filtered":
filtered()
case "recover":
dontDie()
case "session-and-error":
sessionAndError()
case "send-and-exit":
sendAndExit()
case "user":
user()
case "multiple-handled":
multipleHandled()
case "multiple-unhandled":
multipleUnhandled()
case "make-unhandled-with-callback":
handledToUnhandled()
case "nested-error":
nestedHandledError()
default:
log.Println("Not a valid test flag: " + *test)
os.Exit(1)
bugsnag.DefaultSessionPublishInterval = time.Millisecond * 50

// Listening to the OS Signals
ticker := time.NewTicker(1 * time.Second)
for {
select {
case <-ticker.C:
command := GetCommand(addr)
fmt.Printf("[Bugsnag] Received command: %+v\n", command)
if command.Action != "run-scenario" {
continue
}
prepareScenarioFunc, ok := scenariosMap[command.ScenarioName]
if ok {
scenarioFunc := prepareScenarioFunc(command)
scenarioFunc()
time.Sleep(200 * time.Millisecond)
}
}
}

}

func multipleHandled() {
Expand Down
6 changes: 3 additions & 3 deletions features/fixtures/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
version: '3.4'
services:
app:
build:
context: .
dockerfile: app/Dockerfile
context: ../../
dockerfile: ./features/fixtures/app/Dockerfile
args:
- GO_VERSION
environment:
- DEFAULT_MAZE_ADDRESS
- API_KEY
- ERROR_CLASS
- BUGSNAG_ENDPOINT
Expand Down
41 changes: 41 additions & 0 deletions features/steps/go_steps.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
require 'net/http'
require 'os'

When('I run {string}') do |scenario_name|
execute_command 'run-scenario', scenario_name
end

When('I configure the base endpoint') do
steps %(
When I set environment variable "DEFAULT_MAZE_ADDRESS" to "http://#{local_ip}:9339"
)
end

def execute_command(action, scenario_name = '')
address = $address ? $address : "#{local_ip}:9339"

command = {
action: action,
scenario_name: scenario_name,
notify_endpoint: "http://#{address}/notify",
sessions_endpoint: "http://#{address}/sessions",
api_key: $api_key,
}

$logger.debug("Queuing command: #{command}")
Maze::Server.commands.add command

# Ensure fixture has read the command
count = 900
sleep 0.1 until Maze::Server.commands.remaining.empty? || (count -= 1) < 1
raise 'Test fixture did not GET /command' unless Maze::Server.commands.remaining.empty?
end

def local_ip
if OS.mac?
'host.docker.internal'
else
ip_addr = `ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\\.){3}[0-9]*' | grep -v '127.0.0.1'`
ip_list = /((?:[0-9]*\.){3}[0-9]*)/.match(ip_addr)
ip_list.captures.first
end
end

Given("I set environment variable {string} to the app directory") do |key|
step("I set environment variable \"#{key}\" to \"/app/src/test/\"")
Expand Down
23 changes: 7 additions & 16 deletions features/support/env.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
require 'fileutils'
require 'socket'
require 'timeout'

testBuildFolder = 'features/fixtures/testbuild'

FileUtils.rm_rf(testBuildFolder)
Dir.mkdir testBuildFolder

# Copy the existing dir
`find . -name '*.go' -o -name 'go.sum' -o -name 'go.mod' \
-not -path "./examples/*" \
-not -path "./testutil/*" \
-not -path "./v2/testutil/*" \
-not -path "./features/*" \
-not -name '*_test.go' | cpio -pdm #{testBuildFolder}`
Before do
$address = nil
$api_key = "166f5ad3590596f9aa8d601ea89af845"
steps %(
When I configure the base endpoint
)
end
14 changes: 10 additions & 4 deletions v2/headers/prefixed.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package headers

import "time"
import (
"fmt"
"time"
)

// PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for
// API key, payload version, and the time at which the request is being sent.
func PrefixedHeaders(apiKey, payloadVersion, sha1 string) map[string]string {
integrityHeader := fmt.Sprintf("sha1 %v", sha1)

//PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for
//API key, payload version, and the time at which the request is being sent.
func PrefixedHeaders(apiKey, payloadVersion string) map[string]string {
return map[string]string{
"Content-Type": "application/json",
"Bugsnag-Api-Key": apiKey,
"Bugsnag-Payload-Version": payloadVersion,
"Bugsnag-Sent-At": time.Now().UTC().Format(time.RFC3339),
"Bugsnag-Integrity": integrityHeader,
}
}
7 changes: 5 additions & 2 deletions v2/headers/prefixed_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
package headers

import (
"fmt"
"strings"
"testing"
"time"
)

const APIKey = "abcd1234abcd1234"
const testPayloadVersion = "3"
const testSHA = "5e13ae4640ae4ae0e09c05b7bb060f544dabd042"

func TestConstantBugsnagPrefixedHeaders(t *testing.T) {
headers := PrefixedHeaders(APIKey, testPayloadVersion)
headers := PrefixedHeaders(APIKey, testPayloadVersion, testSHA)
testCases := []struct {
header string
expected string
}{
{header: "Content-Type", expected: "application/json"},
{header: "Bugsnag-Api-Key", expected: APIKey},
{header: "Bugsnag-Payload-Version", expected: testPayloadVersion},
{header: "Bugsnag-Integrity", expected: fmt.Sprintf("sha1 %v", testSHA)},
}
for _, tc := range testCases {
t.Run(tc.header, func(st *testing.T) {
Expand All @@ -29,7 +32,7 @@ func TestConstantBugsnagPrefixedHeaders(t *testing.T) {
}

func TestTimeDependentBugsnagPrefixedHeaders(t *testing.T) {
headers := PrefixedHeaders(APIKey, testPayloadVersion)
headers := PrefixedHeaders(APIKey, testPayloadVersion, testSHA)
sentAtString := headers["Bugsnag-Sent-At"]
if !strings.HasSuffix(sentAtString, "Z") {
t.Errorf("Error when setting Bugsnag-Sent-At header: %s, doesn't end with a Z", sentAtString)
Expand Down
Loading
Loading