<commit_before>package tap

import (
	"fmt"
	"net"
	"os"
)

// 48 bits Mac addr
type HwAddr [6]byte

func (h HwAddr) String() string {
	return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", h[0], h[1], h[2], h[3], h[4], h[5])
}

// Interface is the abstract class of an network interface.
type Interface struct {
	tap  bool
	file *os.File
	name string
	mac  HwAddr
}

// Create a new tap device.
// Windows version behaves a little bit differently to Linux version.
func NewTAP() (ifce *Interface, err error) {
	return newTAP()
}

// Returns true if ifce is a TUN interface, otherwise returns false;
func (ifce *Interface) IsTUN() bool {
	return !ifce.tap
}

// Returns true if ifce is a TAP interface, otherwise returns false;
func (ifce *Interface) IsTAP() bool {
	return ifce.tap
}

// Returns the interface name of ifce, e.g. tun0, tap1, etc..
func (ifce *Interface) Name() string {
	return ifce.name
}

// Implement io.Writer interface.
func (ifce *Interface) Write(p []byte) (int, error) {
	return ifce.file.Write(p)
}

// Implement io.Reader interface.
func (ifce *Interface) Read(p []byte) (int, error) {
	return ifce.file.Read(p)
}

// Close the interface.
func (ifce *Interface) Close() error {
	return ifce.file.Close()
}

// Mac address of the interface.
func (ifce *Interface) MacAddr() HwAddr {
	return ifce.mac
}

// Set ip address of the interface.
func (ifce *Interface) SetIP(ip_mask *net.IPNet) error {
	return ifce.setIP(ip_mask)
}

func (ifce *Interface) AddRoute(ip net.IP, ip_mask *net.IPNet) error {
	return ifce.addRoute(ip, ip_mask)
}

func (ifce *Interface) DelRoute(ip net.IP, ip_mask *net.IPNet) error {
	return ifce.delRoute(ip, ip_mask)
}
<commit_msg>Make the tap device thread safe.<commit_after>package tap

import (
	"fmt"
	"net"
	"os"
	"sync"
)

// 48 bits Mac addr
type HwAddr [6]byte

func (h HwAddr) String() string {
	return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", h[0], h[1], h[2], h[3], h[4], h[5])
}

// Interface is the abstract class of an network interface.
type Interface struct {
	rlock sync.Mutex
	wlock sync.Mutex
	tap   bool
	file  *os.File
	name  string
	mac   HwAddr
}

// Create a new tap device.
// Windows version behaves a little bit differently to Linux version.
func NewTAP() (ifce *Interface, err error) {
	return newTAP()
}

// Returns true if ifce is a TUN interface, otherwise returns false;
func (ifce *Interface) IsTUN() bool {
	return !ifce.tap
}

// Returns true if ifce is a TAP interface, otherwise returns false;
func (ifce *Interface) IsTAP() bool {
	return ifce.tap
}

// Returns the interface name of ifce, e.g. tun0, tap1, etc..
func (ifce *Interface) Name() string {
	return ifce.name
}

// Implement io.Writer interface.
func (ifce *Interface) Write(p []byte) (int, error) {
	ifce.wlock.Lock()
	defer ifce.wlock.Unlock()
	return ifce.file.Write(p)
}

// Implement io.Reader interface.
func (ifce *Interface) Read(p []byte) (int, error) {
	ifce.rlock.Lock()
	defer ifce.rlock.Unlock()
	return ifce.file.Read(p)
}

// Close the interface.
func (ifce *Interface) Close() error {
	return ifce.file.Close()
}

// Mac address of the interface.
func (ifce *Interface) MacAddr() HwAddr {
	return ifce.mac
}

// Set ip address of the interface.
func (ifce *Interface) SetIP(ip_mask *net.IPNet) error {
	return ifce.setIP(ip_mask)
}

func (ifce *Interface) AddRoute(ip net.IP, ip_mask *net.IPNet) error {
	return ifce.addRoute(ip, ip_mask)
}

func (ifce *Interface) DelRoute(ip net.IP, ip_mask *net.IPNet) error {
	return ifce.delRoute(ip, ip_mask)
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go.net/html"
	"code.google.com/p/go.net/html/atom"

	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/user"
	"regexp"
	"strings"
)

var barMap = map[string]string{}

type beerInfo struct {
	brewery string
	brew    string
}

func recFind(node *html.Node, result *string, fn func(*html.Node, *string) bool) bool {
	for kid := node.FirstChild; kid != nil; kid = kid.NextSibling {
		if fn(kid, result) {
			return true
		}
	}
	return false
}

func findAttr(node *html.Node, keyRx, valRx string) *html.Attribute {
	for _, attr := range node.Attr {
		ok, err := regexp.MatchString(keyRx, attr.Key)
		if err == nil && ok {
			ok, err := regexp.MatchString(valRx, attr.Val)
			if err == nil && ok {
				return &attr
			}
		}
	}
	return nil
}

func findBeer(node *html.Node, beers *[]beerInfo) {
	if findAttr(node, "^id$", "^beer-\\d+$") != nil {
		brewery, brew := "", ""
		findBrewery(node, &brewery)
		findBrew(node, &brew)
		*beers = append(*beers, beerInfo{brewery, brew})
		return
	}
	for kid := node.FirstChild; kid != nil; kid = kid.NextSibling {
		findBeer(kid, beers)
	}
}

func findBrewery(node *html.Node, brewery *string) bool {
	if node.DataAtom == atom.H4 {
		if content := node.FirstChild; content != nil {
			*brewery = content.Data
			return true
		}
	}
	return recFind(node, brewery, findBrewery)
}

func findBrew(node *html.Node, brew *string) bool {
	if findAttr(node, "^class$", "^beer-name$") != nil {
		if content := node.FirstChild; content != nil {
			*brew = content.Data
			return true
		}
	}
	return recFind(node, brew, findBrew)
}

func findBarDesc(node *html.Node, desc *string) bool {
	if findAttr(node, "^name$", "^description$") != nil {
		if attr := findAttr(node, "^content$", ""); attr != nil {
			*desc = attr.Val
			return true
		}
	}
	return recFind(node, desc, findBarDesc)
}

func checkId(id string) bool {
	ok, err := regexp.MatchString("^[[:xdigit:]]{24}$", id)
	if err != nil {
		return false
	}
	return ok
}

func readRc() {
	usr, err := user.Current()
	if err != nil {
		return
	}
	data, err := ioutil.ReadFile(usr.HomeDir + "/.taplistrc")
	if err != nil {
		return
	}
	lines := strings.Split(string(data), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if idx := strings.Index(line, "#"); idx >= 0 {
			line = line[:idx]
		}
		idx := strings.IndexAny(line, " \t")
		if idx > 0 && idx < len(line)-1 {
			id, name := line[:idx], strings.TrimSpace(line[idx:])
			if checkId(id) && name != "" {
				barMap[id] = name
			}
		}
	}
}

func lookupBar(arg string) (string, string) {
	for id, name := range barMap {
		if strings.Contains(strings.ToLower(name), strings.ToLower(arg)) {
			return id, name
		}
	}
	return "", ""
}

func main() {
	log.SetFlags(0)
	log.SetPrefix("taplist: ")

	if len(os.Args) != 2 {
		log.Fatalln("usage: taplist <id> | <name>")
	}
	readRc()
	arg := strings.ToLower(os.Args[1])
	id, name := "", ""
	if checkId(arg) {
		id, name = arg, arg
	} else {
		id, name = lookupBar(arg)
	}
	if id == "" {
		log.Fatalln(arg + " doesn't look like a valid name or taplister bar id")
	}

	resp, err := http.Get("http://www.taplister.com/bars/" + id)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		log.Fatalln("Sorry, couldn't find what's on tap at " + name)
	}

	page, err := html.Parse(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	desc, beers := "", []beerInfo{}
	findBarDesc(page, &desc)
	findBeer(page, &beers)
	if desc != "" {
		fmt.Println(desc + "\n")
	} else {
		fmt.Printf("%d beers on tap at "+name+"\n\n", len(beers))
	}
	for _, beer := range beers {
		fmt.Printf("%-38.38s  %s\n", beer.brewery, beer.brew)
	}
}
<commit_msg>Check results of findBrewery and findBeer.<commit_after>package main

import (
	"code.google.com/p/go.net/html"
	"code.google.com/p/go.net/html/atom"

	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/user"
	"regexp"
	"strings"
)

var barMap = map[string]string{}

type beerInfo struct {
	brewery string
	brew    string
}

func recFind(node *html.Node, result *string, fn func(*html.Node, *string) bool) bool {
	for kid := node.FirstChild; kid != nil; kid = kid.NextSibling {
		if fn(kid, result) {
			return true
		}
	}
	return false
}

func findAttr(node *html.Node, keyRx, valRx string) *html.Attribute {
	for _, attr := range node.Attr {
		ok, err := regexp.MatchString(keyRx, attr.Key)
		if err == nil && ok {
			ok, err := regexp.MatchString(valRx, attr.Val)
			if err == nil && ok {
				return &attr
			}
		}
	}
	return nil
}

func findBeer(node *html.Node, beers *[]beerInfo) {
	if findAttr(node, "^id$", "^beer-\\d+$") != nil {
		brewery, brew := "", ""
		if findBrewery(node, &brewery) && findBrew(node, &brew) {
			*beers = append(*beers, beerInfo{brewery, brew})
		}
		return
	}
	for kid := node.FirstChild; kid != nil; kid = kid.NextSibling {
		findBeer(kid, beers)
	}
}

func findBrewery(node *html.Node, brewery *string) bool {
	if node.DataAtom == atom.H4 {
		if content := node.FirstChild; content != nil {
			*brewery = content.Data
			return true
		}
	}
	return recFind(node, brewery, findBrewery)
}

func findBrew(node *html.Node, brew *string) bool {
	if findAttr(node, "^class$", "^beer-name$") != nil {
		if content := node.FirstChild; content != nil {
			*brew = content.Data
			return true
		}
	}
	return recFind(node, brew, findBrew)
}

func findBarDesc(node *html.Node, desc *string) bool {
	if findAttr(node, "^name$", "^description$") != nil {
		if attr := findAttr(node, "^content$", ""); attr != nil {
			*desc = attr.Val
			return true
		}
	}
	return recFind(node, desc, findBarDesc)
}

func checkId(id string) bool {
	ok, err := regexp.MatchString("^[[:xdigit:]]{24}$", id)
	if err != nil {
		return false
	}
	return ok
}

func readRc() {
	usr, err := user.Current()
	if err != nil {
		return
	}
	data, err := ioutil.ReadFile(usr.HomeDir + "/.taplistrc")
	if err != nil {
		return
	}
	lines := strings.Split(string(data), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if idx := strings.Index(line, "#"); idx >= 0 {
			line = line[:idx]
		}
		idx := strings.IndexAny(line, " \t")
		if idx > 0 && idx < len(line)-1 {
			id, name := line[:idx], strings.TrimSpace(line[idx:])
			if checkId(id) && name != "" {
				barMap[id] = name
			}
		}
	}
}

func lookupBar(arg string) (string, string) {
	for id, name := range barMap {
		if strings.Contains(strings.ToLower(name), strings.ToLower(arg)) {
			return id, name
		}
	}
	return "", ""
}

func main() {
	log.SetFlags(0)
	log.SetPrefix("taplist: ")

	if len(os.Args) != 2 {
		log.Fatalln("usage: taplist <id> | <name>")
	}
	readRc()
	arg := strings.ToLower(os.Args[1])
	id, name := "", ""
	if checkId(arg) {
		id, name = arg, arg
	} else {
		id, name = lookupBar(arg)
	}
	if id == "" {
		log.Fatalln(arg + " doesn't look like a valid name or taplister bar id")
	}

	resp, err := http.Get("http://www.taplister.com/bars/" + id)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		log.Fatalln("Sorry, couldn't find what's on tap at " + name)
	}

	page, err := html.Parse(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	desc, beers := "", []beerInfo{}
	findBarDesc(page, &desc)
	findBeer(page, &beers)
	if desc != "" {
		fmt.Println(desc + "\n")
	} else {
		fmt.Printf("%d beers on tap at "+name+"\n\n", len(beers))
	}
	for _, beer := range beers {
		fmt.Printf("%-38.38s  %s\n", beer.brewery, beer.brew)
	}
}
<|endoftext|>


<commit_before>package acme

import (
	"crypto/tls"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/square/go-jose"
)

func TestSimpleHTTPCanSolve(t *testing.T) {
	challenge := &simpleHTTPChallenge{}

	// determine public ip
	resp, err := http.Get("https://icanhazip.com/")
	if err != nil {
		t.Errorf("Could not get public IP -> %v", err)
	}

	ip, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Errorf("Could not get public IP -> %v", err)
	}
	ipStr := string(ip)

	if expected, actual := false, challenge.CanSolve("google.com"); expected != actual {
		t.Errorf("Expected CanSolve to return %t for domain 'google.com' but was %t", expected, actual)
	}

	localResolv := strings.Replace(ipStr, "\n", "", -1) + ".xip.io"
	if expected, actual := true, challenge.CanSolve(localResolv); expected != actual {
		t.Errorf("Expected CanSolve to return %t for domain 'localhost' but was %t", expected, actual)
	}
}

func TestSimpleHTTP(t *testing.T) {
	privKey, err := generatePrivateKey(512)
	if err != nil {
		t.Errorf("Could not generate public key -> %v", err)
	}
	jws := &jws{privKey: privKey}

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Replay-Nonce", "12345")
	}))

	solver := &simpleHTTPChallenge{jws: jws}
	clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "123456789"}

	// validate error on non-root bind to 443
	if err = solver.Solve(clientChallenge, "test.domain"); err == nil {
		t.Error("BIND: Expected Solve to return an error but the error was nil.")
	}

	// Validate error on unexpected state
	solver.optPort = "8080"
	if err = solver.Solve(clientChallenge, "test.domain"); err == nil {
		t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.")
	}

	// Validate error on invalid status
	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Replay-Nonce", "12345")
		failed := challenge{Type: "simpleHttp", Status: "invalid", URI: ts.URL, Token: "1234567810"}
		jsonBytes, _ := json.Marshal(&failed)
		w.Write(jsonBytes)
	})
	clientChallenge.Token = "1234567810"
	if err = solver.Solve(clientChallenge, "test.domain"); err == nil {
		t.Error("FAILED: Expected Solve to return an error but the error was nil.")
	}

	// Validate no error on valid response
	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Replay-Nonce", "12345")
		valid := challenge{Type: "simpleHttp", Status: "valid", URI: ts.URL, Token: "1234567811"}
		jsonBytes, _ := json.Marshal(&valid)
		w.Write(jsonBytes)
	})
	clientChallenge.Token = "1234567811"
	if err = solver.Solve(clientChallenge, "test.domain"); err != nil {
		t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err)
	}

	// Validate server on port 8080 which responds appropriately
	clientChallenge.Token = "1234567812"
	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var request challenge
		w.Header().Add("Replay-Nonce", "12345")

		if r.Method == "HEAD" {
			return
		}

		clientJws, _ := ioutil.ReadAll(r.Body)
		j, err := jose.ParseSigned(string(clientJws))
		if err != nil {
			t.Errorf("Client sent invalid JWS to the server.\n\t%v", err)
			return
		}
		output, err := j.Verify(&privKey.PublicKey)
		if err != nil {
			t.Errorf("Unable to verify client data -> %v", err)
		}
		json.Unmarshal(output, &request)

		transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
		client := &http.Client{Transport: transport}

		reqURL := "https://localhost:8080/.well-known/acme-challenge/" + clientChallenge.Token
		t.Logf("Request URL is: %s", reqURL)
		req, _ := http.NewRequest("GET", reqURL, nil)
		req.Host = "test.domain"
		resp, err := client.Do(req)
		if err != nil {
			t.Errorf("Expected the solver to listen on port 8080 -> %v", err)
		}

		body, _ := ioutil.ReadAll(resp.Body)
		bodyStr := string(body)
		clientResponse, err := jose.ParseSigned(bodyStr)
		if err != nil {
			t.Errorf("Client answered with invalid JWS.\n\t%v", err)
			return
		}
		_, err = clientResponse.Verify(&privKey.PublicKey)
		if err != nil {
			t.Errorf("Unable to verify client data -> %v", err)
		}

		valid := challenge{Type: "simpleHttp", Status: "valid", URI: ts.URL, Token: "1234567812"}
		jsonBytes, _ := json.Marshal(&valid)
		w.Write(jsonBytes)
	})
	if err = solver.Solve(clientChallenge, "test.domain"); err != nil {
		t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err)
	}
}
<commit_msg>Move the tests to a different port.<commit_after>package acme

import (
	"crypto/tls"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/square/go-jose"
)

func TestSimpleHTTPCanSolve(t *testing.T) {
	challenge := &simpleHTTPChallenge{}

	// determine public ip
	resp, err := http.Get("https://icanhazip.com/")
	if err != nil {
		t.Errorf("Could not get public IP -> %v", err)
	}

	ip, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Errorf("Could not get public IP -> %v", err)
	}
	ipStr := string(ip)

	if expected, actual := false, challenge.CanSolve("google.com"); expected != actual {
		t.Errorf("Expected CanSolve to return %t for domain 'google.com' but was %t", expected, actual)
	}

	localResolv := strings.Replace(ipStr, "\n", "", -1) + ".xip.io"
	if expected, actual := true, challenge.CanSolve(localResolv); expected != actual {
		t.Errorf("Expected CanSolve to return %t for domain 'localhost' but was %t", expected, actual)
	}
}

func TestSimpleHTTP(t *testing.T) {
	privKey, err := generatePrivateKey(512)
	if err != nil {
		t.Errorf("Could not generate public key -> %v", err)
	}
	jws := &jws{privKey: privKey}

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Replay-Nonce", "12345")
	}))

	solver := &simpleHTTPChallenge{jws: jws}
	clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "123456789"}

	// validate error on non-root bind to 443
	if err = solver.Solve(clientChallenge, "test.domain"); err == nil {
		t.Error("BIND: Expected Solve to return an error but the error was nil.")
	}

	// Validate error on unexpected state
	solver.optPort = "23456"
	if err = solver.Solve(clientChallenge, "test.domain"); err == nil {
		t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.")
	}

	// Validate error on invalid status
	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Replay-Nonce", "12345")
		failed := challenge{Type: "simpleHttp", Status: "invalid", URI: ts.URL, Token: "1234567810"}
		jsonBytes, _ := json.Marshal(&failed)
		w.Write(jsonBytes)
	})
	clientChallenge.Token = "1234567810"
	if err = solver.Solve(clientChallenge, "test.domain"); err == nil {
		t.Error("FAILED: Expected Solve to return an error but the error was nil.")
	}

	// Validate no error on valid response
	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Replay-Nonce", "12345")
		valid := challenge{Type: "simpleHttp", Status: "valid", URI: ts.URL, Token: "1234567811"}
		jsonBytes, _ := json.Marshal(&valid)
		w.Write(jsonBytes)
	})
	clientChallenge.Token = "1234567811"
	if err = solver.Solve(clientChallenge, "test.domain"); err != nil {
		t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err)
	}

	// Validate server on port 23456 which responds appropriately
	clientChallenge.Token = "1234567812"
	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var request challenge
		w.Header().Add("Replay-Nonce", "12345")

		if r.Method == "HEAD" {
			return
		}

		clientJws, _ := ioutil.ReadAll(r.Body)
		j, err := jose.ParseSigned(string(clientJws))
		if err != nil {
			t.Errorf("Client sent invalid JWS to the server.\n\t%v", err)
			return
		}
		output, err := j.Verify(&privKey.PublicKey)
		if err != nil {
			t.Errorf("Unable to verify client data -> %v", err)
		}
		json.Unmarshal(output, &request)

		transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
		client := &http.Client{Transport: transport}

		reqURL := "https://localhost:23456/.well-known/acme-challenge/" + clientChallenge.Token
		t.Logf("Request URL is: %s", reqURL)
		req, _ := http.NewRequest("GET", reqURL, nil)
		req.Host = "test.domain"
		resp, err := client.Do(req)
		if err != nil {
			t.Errorf("Expected the solver to listen on port 23456 -> %v", err)
		}

		body, _ := ioutil.ReadAll(resp.Body)
		bodyStr := string(body)
		clientResponse, err := jose.ParseSigned(bodyStr)
		if err != nil {
			t.Errorf("Client answered with invalid JWS.\n\t%v", err)
			return
		}
		_, err = clientResponse.Verify(&privKey.PublicKey)
		if err != nil {
			t.Errorf("Unable to verify client data -> %v", err)
		}

		valid := challenge{Type: "simpleHttp", Status: "valid", URI: ts.URL, Token: "1234567812"}
		jsonBytes, _ := json.Marshal(&valid)
		w.Write(jsonBytes)
	})
	if err = solver.Solve(clientChallenge, "test.domain"); err != nil {
		t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err)
	}
}
<|endoftext|>


<commit_before>// +build !windows

package termbox

import "unicode/utf8"
import "bytes"
import "syscall"
import "unsafe"
import "strings"
import "strconv"
import "os"
import "io"

// private API

const (
	t_enter_ca = iota
	t_exit_ca
	t_show_cursor
	t_hide_cursor
	t_clear_screen
	t_sgr0
	t_underline
	t_bold
	t_blink
	t_reverse
	t_enter_keypad
	t_exit_keypad
	t_enter_mouse
	t_exit_mouse
	t_max_funcs
)

const (
	coord_invalid = -2
	attr_invalid  = Attribute(0xFFFF)
)

type input_event struct {
	data []byte
	err  error
}

var (
	// term specific sequences
	keys  []string
	funcs []string

	// termbox inner state
	orig_tios    syscall_Termios
	back_buffer  cellbuf
	front_buffer cellbuf
	termw        int
	termh        int
	input_mode   = InputEsc
	out          *os.File
	in           int
	lastfg       = attr_invalid
	lastbg       = attr_invalid
	lastx        = coord_invalid
	lasty        = coord_invalid
	cursor_x     = cursor_hidden
	cursor_y     = cursor_hidden
	foreground   = ColorDefault
	background   = ColorDefault
	inbuf        = make([]byte, 0, 64)
	outbuf       bytes.Buffer
	sigwinch     = make(chan os.Signal, 1)
	sigio        = make(chan os.Signal, 1)
	quit         = make(chan int)
	input_comm   = make(chan input_event)
	intbuf       = make([]byte, 0, 16)
)

func write_cursor(x, y int) {
	outbuf.WriteString("\033[")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
	outbuf.WriteString(";")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
	outbuf.WriteString("H")
}

func write_sgr_fg(a Attribute) {
	outbuf.WriteString("\033[3")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr_bg(a Attribute) {
	outbuf.WriteString("\033[4")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr(fg, bg Attribute) {
	outbuf.WriteString("\033[3")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
	outbuf.WriteString(";4")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
	outbuf.WriteString("m")
}

type winsize struct {
	rows    uint16
	cols    uint16
	xpixels uint16
	ypixels uint16
}

func get_term_size(fd uintptr) (int, int) {
	var sz winsize
	_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
	return int(sz.cols), int(sz.rows)
}

func send_attr(fg, bg Attribute) {
	if fg != lastfg || bg != lastbg {
		outbuf.WriteString(funcs[t_sgr0])
		fgcol := fg & 0x0F
		bgcol := bg & 0x0F
		if fgcol != ColorDefault {
			if bgcol != ColorDefault {
				write_sgr(fgcol, bgcol)
			} else {
				write_sgr_fg(fgcol)
			}
		} else if bgcol != ColorDefault {
			write_sgr_bg(bgcol)
		}

		if fg&AttrBold != 0 {
			outbuf.WriteString(funcs[t_bold])
		}
		if bg&AttrBold != 0 {
			outbuf.WriteString(funcs[t_blink])
		}
		if fg&AttrUnderline != 0 {
			outbuf.WriteString(funcs[t_underline])
		}
		if fg&AttrReverse|bg&AttrReverse != 0 {
			outbuf.WriteString(funcs[t_reverse])
		}

		lastfg, lastbg = fg, bg
	}
}

func send_char(x, y int, ch rune) {
	var buf [8]byte
	n := utf8.EncodeRune(buf[:], ch)
	if x-1 != lastx || y != lasty {
		write_cursor(x, y)
	}
	lastx, lasty = x, y
	outbuf.Write(buf[:n])
}

func flush() error {
	_, err := io.Copy(out, &outbuf)
	outbuf.Reset()
	if err != nil {
		return err
	}
	return nil
}

func send_clear() error {
	send_attr(foreground, background)
	outbuf.WriteString(funcs[t_clear_screen])
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}

	// we need to invalidate cursor position too and these two vars are
	// used only for simple cursor positioning optimization, cursor
	// actually may be in the correct place, but we simply discard
	// optimization once and it gives us simple solution for the case when
	// cursor moved
	lastx = coord_invalid
	lasty = coord_invalid

	return flush()
}

func update_size_maybe() error {
	w, h := get_term_size(out.Fd())
	if w != termw || h != termh {
		termw, termh = w, h
		back_buffer.resize(termw, termh)
		front_buffer.resize(termw, termh)
		front_buffer.clear()
		return send_clear()
	}
	return nil
}

func tcsetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func tcgetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func parse_escape_sequence(event *Event, buf []byte) int {
	bufstr := string(buf)
	// mouse
	if len(bufstr) >= 6 && strings.HasPrefix(bufstr, "\033[M") {
		event.Type = EventMouse // KeyEvent by default
		switch buf[3] & 3 {
		case 0:
			event.Key = MouseLeft
		case 1:
			event.Key = MouseMid
		case 2:
			event.Key = MouseRight
		}
		// wheel up outputs MouseLeft
		if buf[3] == 0x60 {
			event.Key = MouseMid
		}
		// the coord is 1,1 for upper left
		event.MouseX = int(buf[4]) - 1 - 32
		event.MouseY = int(buf[5]) - 1 - 32
		return 6
	}

	for i, key := range keys {
		if strings.HasPrefix(bufstr, key) {
			event.Ch = 0
			event.Key = Key(0xFFFF - i)
			return len(key)
		}
	}
	return 0
}

func extract_event(event *Event) bool {
	if len(inbuf) == 0 {
		return false
	}

	if inbuf[0] == '\033' {
		// possible escape sequence
		n := parse_escape_sequence(event, inbuf)
		if n != 0 {
			copy(inbuf, inbuf[n:])
			inbuf = inbuf[:len(inbuf)-n]
			return true
		}

		// it's not escape sequence, then it's Alt or Esc, check input_mode
		switch input_mode {
		case InputEsc:
			// if we're in escape mode, fill Esc event, pop buffer, return success
			event.Ch = 0
			event.Key = KeyEsc
			event.Mod = 0
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return true
		case InputAlt:
			// if we're in alt mode, set Alt modifier to event and redo parsing
			event.Mod = ModAlt
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return extract_event(event)
		default:
			panic("unreachable")
		}
	}

	// if we're here, this is not an escape sequence and not an alt sequence
	// so, it's a FUNCTIONAL KEY or a UNICODE character

	// first of all check if it's a functional key
	if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
		// fill event, pop buffer, return success
		event.Ch = 0
		event.Key = Key(inbuf[0])
		copy(inbuf, inbuf[1:])
		inbuf = inbuf[:len(inbuf)-1]
		return true
	}

	// the only possible option is utf8 rune
	if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
		event.Ch = r
		event.Key = 0
		copy(inbuf, inbuf[n:])
		inbuf = inbuf[:len(inbuf)-n]
		return true
	}

	return false
}

func fcntl(fd int, cmd int, arg int) (val int, err error) {
	r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
		uintptr(arg))
	val = int(r)
	if e != 0 {
		err = e
	}
	return
}
<commit_msg>ignore mouse release for linux<commit_after>// +build !windows

package termbox

import "unicode/utf8"
import "bytes"
import "syscall"
import "unsafe"
import "strings"
import "strconv"
import "os"
import "io"

// private API

const (
	t_enter_ca = iota
	t_exit_ca
	t_show_cursor
	t_hide_cursor
	t_clear_screen
	t_sgr0
	t_underline
	t_bold
	t_blink
	t_reverse
	t_enter_keypad
	t_exit_keypad
	t_enter_mouse
	t_exit_mouse
	t_max_funcs
)

const (
	coord_invalid = -2
	attr_invalid  = Attribute(0xFFFF)
)

type input_event struct {
	data []byte
	err  error
}

var (
	// term specific sequences
	keys  []string
	funcs []string

	// termbox inner state
	orig_tios    syscall_Termios
	back_buffer  cellbuf
	front_buffer cellbuf
	termw        int
	termh        int
	input_mode   = InputEsc
	out          *os.File
	in           int
	lastfg       = attr_invalid
	lastbg       = attr_invalid
	lastx        = coord_invalid
	lasty        = coord_invalid
	cursor_x     = cursor_hidden
	cursor_y     = cursor_hidden
	foreground   = ColorDefault
	background   = ColorDefault
	inbuf        = make([]byte, 0, 64)
	outbuf       bytes.Buffer
	sigwinch     = make(chan os.Signal, 1)
	sigio        = make(chan os.Signal, 1)
	quit         = make(chan int)
	input_comm   = make(chan input_event)
	intbuf       = make([]byte, 0, 16)
)

func write_cursor(x, y int) {
	outbuf.WriteString("\033[")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
	outbuf.WriteString(";")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
	outbuf.WriteString("H")
}

func write_sgr_fg(a Attribute) {
	outbuf.WriteString("\033[3")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr_bg(a Attribute) {
	outbuf.WriteString("\033[4")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr(fg, bg Attribute) {
	outbuf.WriteString("\033[3")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
	outbuf.WriteString(";4")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
	outbuf.WriteString("m")
}

type winsize struct {
	rows    uint16
	cols    uint16
	xpixels uint16
	ypixels uint16
}

func get_term_size(fd uintptr) (int, int) {
	var sz winsize
	_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
	return int(sz.cols), int(sz.rows)
}

func send_attr(fg, bg Attribute) {
	if fg != lastfg || bg != lastbg {
		outbuf.WriteString(funcs[t_sgr0])
		fgcol := fg & 0x0F
		bgcol := bg & 0x0F
		if fgcol != ColorDefault {
			if bgcol != ColorDefault {
				write_sgr(fgcol, bgcol)
			} else {
				write_sgr_fg(fgcol)
			}
		} else if bgcol != ColorDefault {
			write_sgr_bg(bgcol)
		}

		if fg&AttrBold != 0 {
			outbuf.WriteString(funcs[t_bold])
		}
		if bg&AttrBold != 0 {
			outbuf.WriteString(funcs[t_blink])
		}
		if fg&AttrUnderline != 0 {
			outbuf.WriteString(funcs[t_underline])
		}
		if fg&AttrReverse|bg&AttrReverse != 0 {
			outbuf.WriteString(funcs[t_reverse])
		}

		lastfg, lastbg = fg, bg
	}
}

func send_char(x, y int, ch rune) {
	var buf [8]byte
	n := utf8.EncodeRune(buf[:], ch)
	if x-1 != lastx || y != lasty {
		write_cursor(x, y)
	}
	lastx, lasty = x, y
	outbuf.Write(buf[:n])
}

func flush() error {
	_, err := io.Copy(out, &outbuf)
	outbuf.Reset()
	if err != nil {
		return err
	}
	return nil
}

func send_clear() error {
	send_attr(foreground, background)
	outbuf.WriteString(funcs[t_clear_screen])
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}

	// we need to invalidate cursor position too and these two vars are
	// used only for simple cursor positioning optimization, cursor
	// actually may be in the correct place, but we simply discard
	// optimization once and it gives us simple solution for the case when
	// cursor moved
	lastx = coord_invalid
	lasty = coord_invalid

	return flush()
}

func update_size_maybe() error {
	w, h := get_term_size(out.Fd())
	if w != termw || h != termh {
		termw, termh = w, h
		back_buffer.resize(termw, termh)
		front_buffer.resize(termw, termh)
		front_buffer.clear()
		return send_clear()
	}
	return nil
}

func tcsetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func tcgetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
	bufstr := string(buf)
	// mouse
	if len(bufstr) >= 6 && strings.HasPrefix(bufstr, "\033[M") {
		mouseRelease := false
		switch buf[3] & 3 {
		case 0:
			event.Key = MouseLeft
		case 1:
			event.Key = MouseMid
		case 2:
			event.Key = MouseRight
		case 3:
			mouseRelease = true
		}
		if mouseRelease {
			return 6, false
		} else {
			event.Type = EventMouse // KeyEvent by default
			// wheel up outputs MouseLeft
			if buf[3] == 0x60 {
				event.Key = MouseMid
			}
			// the coord is 1,1 for upper left
			event.MouseX = int(buf[4]) - 1 - 32
			event.MouseY = int(buf[5]) - 1 - 32
			return 6, true
		}
	}

	for i, key := range keys {
		if strings.HasPrefix(bufstr, key) {
			event.Ch = 0
			event.Key = Key(0xFFFF - i)
			return len(key), true
		}
	}
	return 0, true
}

func extract_event(event *Event) bool {
	if len(inbuf) == 0 {
		return false
	}

	if inbuf[0] == '\033' {
		// possible escape sequence
		n, ok := parse_escape_sequence(event, inbuf)
		if n != 0 {
			copy(inbuf, inbuf[n:])
			inbuf = inbuf[:len(inbuf)-n]
			return ok
		}

		// it's not escape sequence, then it's Alt or Esc, check input_mode
		switch input_mode {
		case InputEsc:
			// if we're in escape mode, fill Esc event, pop buffer, return success
			event.Ch = 0
			event.Key = KeyEsc
			event.Mod = 0
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return true
		case InputAlt:
			// if we're in alt mode, set Alt modifier to event and redo parsing
			event.Mod = ModAlt
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return extract_event(event)
		default:
			panic("unreachable")
		}
	}

	// if we're here, this is not an escape sequence and not an alt sequence
	// so, it's a FUNCTIONAL KEY or a UNICODE character

	// first of all check if it's a functional key
	if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
		// fill event, pop buffer, return success
		event.Ch = 0
		event.Key = Key(inbuf[0])
		copy(inbuf, inbuf[1:])
		inbuf = inbuf[:len(inbuf)-1]
		return true
	}

	// the only possible option is utf8 rune
	if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
		event.Ch = r
		event.Key = 0
		copy(inbuf, inbuf[n:])
		inbuf = inbuf[:len(inbuf)-n]
		return true
	}

	return false
}

func fcntl(fd int, cmd int, arg int) (val int, err error) {
	r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
		uintptr(arg))
	val = int(r)
	if e != 0 {
		err = e
	}
	return
}
<|endoftext|>


<commit_before>package sharedaction_test

import (
	"context"
	"errors"
	"fmt"
	"time"

	"code.cloudfoundry.org/cli/actor/sharedaction"
	"code.cloudfoundry.org/cli/actor/sharedaction/sharedactionfakes"
	"code.cloudfoundry.org/go-loggregator/rpc/loggregator_v2"
	logcache "code.cloudfoundry.org/log-cache/pkg/client"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Logging Actions", func() {
	var (
		fakeLogCacheClient *sharedactionfakes.FakeLogCacheClient
	)

	BeforeEach(func() {
		fakeLogCacheClient = new(sharedactionfakes.FakeLogCacheClient)
	})

	Describe("LogMessage", func() {
		Describe("Staging", func() {
			When("the log is a staging log", func() {
				It("returns true", func() {
					message := *sharedaction.NewLogMessage(
						"some-message",
						"OUT",
						time.Unix(0, 0),
						"STG",
						"some-source-instance",
					)

					Expect(message.Staging()).To(BeTrue())
				})
			})

			When("the log is any other kind of log", func() {
				It("returns false", func() {
					message := *sharedaction.NewLogMessage(
						"some-message",
						"OUT",
						time.Unix(0, 0),
						"APP",
						"some-source-instance",
					)
					Expect(message.Staging()).To(BeFalse())
				})
			})
		})
	})

	Describe("GetStreamingLogs", func() {
		var (
			expectedAppGUID string

			messages              <-chan sharedaction.LogMessage
			errs                  <-chan error
			stopStreaming         context.CancelFunc
			mostRecentTime        time.Time
			mostRecentEnvelope    loggregator_v2.Envelope
			slightlyOlderEnvelope loggregator_v2.Envelope
		)

		BeforeEach(func() {
			expectedAppGUID = "some-app-guid"
			// 2 seconds in the past to get past Walk delay
			// Walk delay context: https://github.com/cloudfoundry/cli/blob/b8324096a3d5a495bdcae9d1e7f6267ff135fe82/vendor/code.cloudfoundry.org/log-cache/pkg/client/walk.go#L74
			mostRecentTime = time.Now().Add(-2 * time.Second)
			mostRecentTimestamp := mostRecentTime.UnixNano()
			slightlyOlderTimestamp := mostRecentTime.Add(-500 * time.Millisecond).UnixNano()

			mostRecentEnvelope = loggregator_v2.Envelope{
				Timestamp:  mostRecentTimestamp,
				SourceId:   "some-app-guid",
				InstanceId: "some-source-instance",
				Message: &loggregator_v2.Envelope_Log{
					Log: &loggregator_v2.Log{
						Payload: []byte("message-2"),
						Type:    loggregator_v2.Log_OUT,
					},
				},
				Tags: map[string]string{
					"source_type": "some-source-type",
				},
			}

			slightlyOlderEnvelope = loggregator_v2.Envelope{
				Timestamp:  slightlyOlderTimestamp,
				SourceId:   "some-app-guid",
				InstanceId: "some-source-instance",
				Message: &loggregator_v2.Envelope_Log{
					Log: &loggregator_v2.Log{
						Payload: []byte("message-1"),
						Type:    loggregator_v2.Log_OUT,
					},
				},
				Tags: map[string]string{
					"source_type": "some-source-type",
				},
			}
		})

		AfterEach(func() {
			Eventually(messages).Should(BeClosed())
			Eventually(errs).Should(BeClosed())
		})

		JustBeforeEach(func() {
			messages, errs, stopStreaming = sharedaction.GetStreamingLogs(expectedAppGUID, fakeLogCacheClient)
		})

		When("receiving logs", func() {
			var walkStartTime time.Time

			BeforeEach(func() {
				fakeLogCacheClient.ReadStub = func(
					ctx context.Context,
					sourceID string,
					start time.Time,
					opts ...logcache.ReadOption,
				) ([]*loggregator_v2.Envelope, error) {
					if fakeLogCacheClient.ReadCallCount() > 2 {
						stopStreaming()
						return []*loggregator_v2.Envelope{}, ctx.Err()
					}

					if start.IsZero() {
						return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err()
					}

					walkStartTime = start
					return []*loggregator_v2.Envelope{&slightlyOlderEnvelope, &mostRecentEnvelope}, ctx.Err()
				}
			})

			It("it starts walking at 1 second previous to the mostRecentEnvelope's time", func() {
				Eventually(messages).Should(BeClosed())
				Expect(walkStartTime).To(BeTemporally("~", mostRecentTime.Add(-1*time.Second), time.Millisecond))
			})

			It("converts them to log messages and passes them through the messages channel", func() {
				Eventually(messages).Should(HaveLen(2))
				var message sharedaction.LogMessage
				Expect(messages).To(Receive(&message))
				Expect(message.Message()).To(Equal("message-1"))
				Expect(messages).To(Receive(&message))
				Expect(message.Message()).To(Equal("message-2"))

				Expect(errs).ToNot(Receive())
			})
		})

		When("cancelling log streaming", func() {
			BeforeEach(func() {
				fakeLogCacheClient.ReadStub = func(
					ctx context.Context,
					sourceID string,
					start time.Time,
					opts ...logcache.ReadOption,
				) ([]*loggregator_v2.Envelope, error) {
					return []*loggregator_v2.Envelope{}, ctx.Err()
				}
			})

			It("can be called multiple times", func() {
				Expect(stopStreaming).ToNot(Panic())
				Expect(stopStreaming).ToNot(Panic())
			})
		})

		Describe("error handling", func() {
			When("there is an error 'peeking' at log-cache to determine the latest log", func() {
				BeforeEach(func() {
					fakeLogCacheClient.ReadStub = func(
						ctx context.Context,
						sourceID string,
						start time.Time,
						opts ...logcache.ReadOption,
					) ([]*loggregator_v2.Envelope, error) {
						return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount())
					}
				})

				AfterEach(func() {
					stopStreaming()
				})

				It("passes 5 errors through the errors channel", func() {
					Eventually(errs, 2*time.Second).Should(HaveLen(5))
					Eventually(errs).Should(Receive(MatchError("error number 1")))
					Eventually(errs).Should(Receive(MatchError("error number 2")))
					Eventually(errs).Should(Receive(MatchError("error number 3")))
					Eventually(errs).Should(Receive(MatchError("error number 4")))
					Eventually(errs).Should(Receive(MatchError("error number 5")))
					Consistently(errs).ShouldNot(Receive())
				})

				It("tries exactly 5 times", func() {
					Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5))
					Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5))
				})
			})

			When("there is an error walking log-cache to retrieve logs", func() {
				BeforeEach(func() {
					fakeLogCacheClient.ReadStub = func(
						ctx context.Context,
						sourceID string,
						start time.Time,
						opts ...logcache.ReadOption,
					) ([]*loggregator_v2.Envelope, error) {
						if start.IsZero() {
							return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err()
						}
						return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount()-1)
					}
				})

				AfterEach(func() {
					stopStreaming()
				})

				It("passes 5 errors through the errors channel", func() {
					Eventually(errs, 2*time.Second).Should(HaveLen(5))
					Eventually(errs).Should(Receive(MatchError("error number 1")))
					Eventually(errs).Should(Receive(MatchError("error number 2")))
					Eventually(errs).Should(Receive(MatchError("error number 3")))
					Eventually(errs).Should(Receive(MatchError("error number 4")))
					Eventually(errs).Should(Receive(MatchError("error number 5")))
					Consistently(errs).ShouldNot(Receive())
				})

				It("tries exactly 5 times", func() {
					initialPeekingRead := 1
					walkRetries := 5
					expectedReadCallCount := initialPeekingRead + walkRetries

					Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount))
					Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount))
				})
			})
		})
	})

	Describe("GetRecentLogs", func() {
		When("the application can be found", func() {
			When("Log Cache returns logs", func() {
				BeforeEach(func() {
					messages := []*loggregator_v2.Envelope{
						{
							Timestamp:  int64(20),
							SourceId:   "some-app-guid",
							InstanceId: "some-source-instance",
							Message: &loggregator_v2.Envelope_Log{
								Log: &loggregator_v2.Log{
									Payload: []byte("message-2"),
									Type:    loggregator_v2.Log_OUT,
								},
							},
							Tags: map[string]string{
								"source_type": "some-source-type",
							},
						},
						{
							Timestamp:  int64(10),
							SourceId:   "some-app-guid",
							InstanceId: "some-source-instance",
							Message: &loggregator_v2.Envelope_Log{
								Log: &loggregator_v2.Log{
									Payload: []byte("message-1"),
									Type:    loggregator_v2.Log_OUT,
								},
							},
							Tags: map[string]string{
								"source_type": "some-source-type",
							},
						},
					}

					fakeLogCacheClient.ReadReturns(messages, nil)
				})

				It("returns all the recent logs and warnings", func() {
					messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
					Expect(err).ToNot(HaveOccurred())

					Expect(messages[0].Message()).To(Equal("message-1"))
					Expect(messages[0].Type()).To(Equal("OUT"))
					Expect(messages[0].Timestamp()).To(Equal(time.Unix(0, 10)))
					Expect(messages[0].SourceType()).To(Equal("some-source-type"))
					Expect(messages[0].SourceInstance()).To(Equal("some-source-instance"))

					Expect(messages[1].Message()).To(Equal("message-2"))
					Expect(messages[1].Type()).To(Equal("OUT"))
					Expect(messages[1].Timestamp()).To(Equal(time.Unix(0, 20)))
					Expect(messages[1].SourceType()).To(Equal("some-source-type"))
					Expect(messages[1].SourceInstance()).To(Equal("some-source-instance"))
				})
			})

			When("Log Cache returns non-log envelopes", func() {
				BeforeEach(func() {
					messages := []*loggregator_v2.Envelope{
						{
							Timestamp:  int64(10),
							SourceId:   "some-app-guid",
							InstanceId: "some-source-instance",
							Message:    &loggregator_v2.Envelope_Counter{},
							Tags: map[string]string{
								"source_type": "some-source-type",
							},
						},
					}

					fakeLogCacheClient.ReadReturns(messages, nil)
				})

				It("ignores them", func() {
					messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
					Expect(err).ToNot(HaveOccurred())
					Expect(messages).To(BeEmpty())
				})
			})

			When("Log Cache errors", func() {
				BeforeEach(func() {
					fakeLogCacheClient.ReadReturns(nil, errors.New("some-recent-logs-error"))
				})

				It("returns error and warnings", func() {
					_, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
					Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: some-recent-logs-error"))
				})
			})

			When("Log Cache returns a resource-exhausted error from grpc", func() {
				resourceExhaustedErr := errors.New("unexpected status code 429")
				BeforeEach(func() {
					fakeLogCacheClient.ReadReturns([]*loggregator_v2.Envelope{}, resourceExhaustedErr)
				})

				It("returns error and warnings", func() {
					_, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
					Expect(fakeLogCacheClient.ReadCallCount()).To(Equal(10))
					Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: unexpected status code 429"))
				})
			})
		})
	})

})
<commit_msg>Test halving requested logs on HTTP 429 errors<commit_after>package sharedaction_test

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"time"

	"code.cloudfoundry.org/cli/actor/sharedaction"
	"code.cloudfoundry.org/cli/actor/sharedaction/sharedactionfakes"
	"code.cloudfoundry.org/go-loggregator/rpc/loggregator_v2"
	logcache "code.cloudfoundry.org/log-cache/pkg/client"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Logging Actions", func() {
	var (
		fakeLogCacheClient *sharedactionfakes.FakeLogCacheClient
	)

	BeforeEach(func() {
		fakeLogCacheClient = new(sharedactionfakes.FakeLogCacheClient)
	})

	Describe("LogMessage", func() {
		Describe("Staging", func() {
			When("the log is a staging log", func() {
				It("returns true", func() {
					message := *sharedaction.NewLogMessage(
						"some-message",
						"OUT",
						time.Unix(0, 0),
						"STG",
						"some-source-instance",
					)

					Expect(message.Staging()).To(BeTrue())
				})
			})

			When("the log is any other kind of log", func() {
				It("returns false", func() {
					message := *sharedaction.NewLogMessage(
						"some-message",
						"OUT",
						time.Unix(0, 0),
						"APP",
						"some-source-instance",
					)
					Expect(message.Staging()).To(BeFalse())
				})
			})
		})
	})

	Describe("GetStreamingLogs", func() {
		var (
			expectedAppGUID string

			messages              <-chan sharedaction.LogMessage
			errs                  <-chan error
			stopStreaming         context.CancelFunc
			mostRecentTime        time.Time
			mostRecentEnvelope    loggregator_v2.Envelope
			slightlyOlderEnvelope loggregator_v2.Envelope
		)

		BeforeEach(func() {
			expectedAppGUID = "some-app-guid"
			// 2 seconds in the past to get past Walk delay
			// Walk delay context: https://github.com/cloudfoundry/cli/blob/b8324096a3d5a495bdcae9d1e7f6267ff135fe82/vendor/code.cloudfoundry.org/log-cache/pkg/client/walk.go#L74
			mostRecentTime = time.Now().Add(-2 * time.Second)
			mostRecentTimestamp := mostRecentTime.UnixNano()
			slightlyOlderTimestamp := mostRecentTime.Add(-500 * time.Millisecond).UnixNano()

			mostRecentEnvelope = loggregator_v2.Envelope{
				Timestamp:  mostRecentTimestamp,
				SourceId:   "some-app-guid",
				InstanceId: "some-source-instance",
				Message: &loggregator_v2.Envelope_Log{
					Log: &loggregator_v2.Log{
						Payload: []byte("message-2"),
						Type:    loggregator_v2.Log_OUT,
					},
				},
				Tags: map[string]string{
					"source_type": "some-source-type",
				},
			}

			slightlyOlderEnvelope = loggregator_v2.Envelope{
				Timestamp:  slightlyOlderTimestamp,
				SourceId:   "some-app-guid",
				InstanceId: "some-source-instance",
				Message: &loggregator_v2.Envelope_Log{
					Log: &loggregator_v2.Log{
						Payload: []byte("message-1"),
						Type:    loggregator_v2.Log_OUT,
					},
				},
				Tags: map[string]string{
					"source_type": "some-source-type",
				},
			}
		})

		AfterEach(func() {
			Eventually(messages).Should(BeClosed())
			Eventually(errs).Should(BeClosed())
		})

		JustBeforeEach(func() {
			messages, errs, stopStreaming = sharedaction.GetStreamingLogs(expectedAppGUID, fakeLogCacheClient)
		})

		When("receiving logs", func() {
			var walkStartTime time.Time

			BeforeEach(func() {
				fakeLogCacheClient.ReadStub = func(
					ctx context.Context,
					sourceID string,
					start time.Time,
					opts ...logcache.ReadOption,
				) ([]*loggregator_v2.Envelope, error) {
					if fakeLogCacheClient.ReadCallCount() > 2 {
						stopStreaming()
						return []*loggregator_v2.Envelope{}, ctx.Err()
					}

					if start.IsZero() {
						return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err()
					}

					walkStartTime = start
					return []*loggregator_v2.Envelope{&slightlyOlderEnvelope, &mostRecentEnvelope}, ctx.Err()
				}
			})

			It("it starts walking at 1 second previous to the mostRecentEnvelope's time", func() {
				Eventually(messages).Should(BeClosed())
				Expect(walkStartTime).To(BeTemporally("~", mostRecentTime.Add(-1*time.Second), time.Millisecond))
			})

			It("converts them to log messages and passes them through the messages channel", func() {
				Eventually(messages).Should(HaveLen(2))
				var message sharedaction.LogMessage
				Expect(messages).To(Receive(&message))
				Expect(message.Message()).To(Equal("message-1"))
				Expect(messages).To(Receive(&message))
				Expect(message.Message()).To(Equal("message-2"))

				Expect(errs).ToNot(Receive())
			})
		})

		When("cancelling log streaming", func() {
			BeforeEach(func() {
				fakeLogCacheClient.ReadStub = func(
					ctx context.Context,
					sourceID string,
					start time.Time,
					opts ...logcache.ReadOption,
				) ([]*loggregator_v2.Envelope, error) {
					return []*loggregator_v2.Envelope{}, ctx.Err()
				}
			})

			It("can be called multiple times", func() {
				Expect(stopStreaming).ToNot(Panic())
				Expect(stopStreaming).ToNot(Panic())
			})
		})

		Describe("error handling", func() {
			When("there is an error 'peeking' at log-cache to determine the latest log", func() {
				BeforeEach(func() {
					fakeLogCacheClient.ReadStub = func(
						ctx context.Context,
						sourceID string,
						start time.Time,
						opts ...logcache.ReadOption,
					) ([]*loggregator_v2.Envelope, error) {
						return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount())
					}
				})

				AfterEach(func() {
					stopStreaming()
				})

				It("passes 5 errors through the errors channel", func() {
					Eventually(errs, 2*time.Second).Should(HaveLen(5))
					Eventually(errs).Should(Receive(MatchError("error number 1")))
					Eventually(errs).Should(Receive(MatchError("error number 2")))
					Eventually(errs).Should(Receive(MatchError("error number 3")))
					Eventually(errs).Should(Receive(MatchError("error number 4")))
					Eventually(errs).Should(Receive(MatchError("error number 5")))
					Consistently(errs).ShouldNot(Receive())
				})

				It("tries exactly 5 times", func() {
					Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5))
					Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5))
				})
			})

			When("there is an error walking log-cache to retrieve logs", func() {
				BeforeEach(func() {
					fakeLogCacheClient.ReadStub = func(
						ctx context.Context,
						sourceID string,
						start time.Time,
						opts ...logcache.ReadOption,
					) ([]*loggregator_v2.Envelope, error) {
						if start.IsZero() {
							return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err()
						}
						return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount()-1)
					}
				})

				AfterEach(func() {
					stopStreaming()
				})

				It("passes 5 errors through the errors channel", func() {
					Eventually(errs, 2*time.Second).Should(HaveLen(5))
					Eventually(errs).Should(Receive(MatchError("error number 1")))
					Eventually(errs).Should(Receive(MatchError("error number 2")))
					Eventually(errs).Should(Receive(MatchError("error number 3")))
					Eventually(errs).Should(Receive(MatchError("error number 4")))
					Eventually(errs).Should(Receive(MatchError("error number 5")))
					Consistently(errs).ShouldNot(Receive())
				})

				It("tries exactly 5 times", func() {
					initialPeekingRead := 1
					walkRetries := 5
					expectedReadCallCount := initialPeekingRead + walkRetries

					Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount))
					Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount))
				})
			})
		})
	})

	Describe("GetRecentLogs", func() {
		When("the application can be found", func() {
			When("Log Cache returns logs", func() {
				BeforeEach(func() {
					messages := []*loggregator_v2.Envelope{
						{
							Timestamp:  int64(20),
							SourceId:   "some-app-guid",
							InstanceId: "some-source-instance",
							Message: &loggregator_v2.Envelope_Log{
								Log: &loggregator_v2.Log{
									Payload: []byte("message-2"),
									Type:    loggregator_v2.Log_OUT,
								},
							},
							Tags: map[string]string{
								"source_type": "some-source-type",
							},
						},
						{
							Timestamp:  int64(10),
							SourceId:   "some-app-guid",
							InstanceId: "some-source-instance",
							Message: &loggregator_v2.Envelope_Log{
								Log: &loggregator_v2.Log{
									Payload: []byte("message-1"),
									Type:    loggregator_v2.Log_OUT,
								},
							},
							Tags: map[string]string{
								"source_type": "some-source-type",
							},
						},
					}

					fakeLogCacheClient.ReadReturns(messages, nil)
				})

				It("returns all the recent logs and warnings", func() {
					messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
					Expect(err).ToNot(HaveOccurred())

					Expect(messages[0].Message()).To(Equal("message-1"))
					Expect(messages[0].Type()).To(Equal("OUT"))
					Expect(messages[0].Timestamp()).To(Equal(time.Unix(0, 10)))
					Expect(messages[0].SourceType()).To(Equal("some-source-type"))
					Expect(messages[0].SourceInstance()).To(Equal("some-source-instance"))

					Expect(messages[1].Message()).To(Equal("message-2"))
					Expect(messages[1].Type()).To(Equal("OUT"))
					Expect(messages[1].Timestamp()).To(Equal(time.Unix(0, 20)))
					Expect(messages[1].SourceType()).To(Equal("some-source-type"))
					Expect(messages[1].SourceInstance()).To(Equal("some-source-instance"))
				})
			})

			When("Log Cache returns non-log envelopes", func() {
				BeforeEach(func() {
					messages := []*loggregator_v2.Envelope{
						{
							Timestamp:  int64(10),
							SourceId:   "some-app-guid",
							InstanceId: "some-source-instance",
							Message:    &loggregator_v2.Envelope_Counter{},
							Tags: map[string]string{
								"source_type": "some-source-type",
							},
						},
					}

					fakeLogCacheClient.ReadReturns(messages, nil)
				})

				It("ignores them", func() {
					messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
					Expect(err).ToNot(HaveOccurred())
					Expect(messages).To(BeEmpty())
				})
			})

			When("Log Cache errors", func() {
				BeforeEach(func() {
					fakeLogCacheClient.ReadReturns(nil, errors.New("some-recent-logs-error"))
				})

				It("returns error and warnings", func() {
					_, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
					Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: some-recent-logs-error"))
				})
			})

			When("Log Cache returns a resource-exhausted error from grpc", func() {
				resourceExhaustedErr := errors.New("unexpected status code 429")
				u := new(url.URL)
				v := make(url.Values)

				BeforeEach(func() {
					fakeLogCacheClient.ReadReturns([]*loggregator_v2.Envelope{}, resourceExhaustedErr)
				})

				It("attempts to halve numbber of requested logs, and eventually returns error and warnings", func() {
					_, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)

					Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: unexpected status code 429"))
					Expect(fakeLogCacheClient.ReadCallCount()).To(Equal(10))

					_, _, _, readOptions := fakeLogCacheClient.ReadArgsForCall(0)
					readOptions[1](u, v)
					Expect(v.Get("limit")).To(Equal("1000"))

					_, _, _, readOptions = fakeLogCacheClient.ReadArgsForCall(1)
					readOptions[1](u, v)
					Expect(v.Get("limit")).To(Equal("500"))
				})
			})
		})
	})

})
<|endoftext|>


<commit_before>package resty

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"reflect"
	"testing"
)

var (
	LogRequests = false
)

type TestRequest struct {
	Method string
	Path   string
	Data   interface{}

	ExpectedStatus int
	ExpectedData   interface{}
}

func (tr *TestRequest) String() string {
	return tr.Method + " " + tr.Path
}

func (tr *TestRequest) Run(t *testing.T, c *Client) {
	r := c.Do(tr.Method, tr.Path, tr.Data, nil)
	if LogRequests {
		t.Logf("%s: %s", tr.String(), r.Value)
	}

	switch {
	case r.Err != nil:
		t.Fatalf("%s: error: %v, status: %d, resp: %s", tr.String(), r.Err, r.Status, r.Value)
	case tr.ExpectedStatus == 0 && r.Status != 200, r.Status != tr.ExpectedStatus:
		t.Fatalf("%s: wanted %d, got %d: %s", tr.String(), tr.ExpectedStatus, r.Status, r.Value)
	case tr.ExpectedData != nil:
		if err := compareRes(r.Value, getVal(tr.ExpectedData)); err != nil {
			t.Fatalf("%s: %v", tr.String(), err)
		}
	}
}

// a == result, b == expected
func compareRes(a, b []byte) error {
	var am, bm interface{}
	if err := json.Unmarshal(a, &am); err != nil {
		return fmt.Errorf("%s: %v", a, err)
	}
	if err := json.Unmarshal(b, &bm); err != nil {
		return fmt.Errorf("%s: %v", b, err)
	}

	return cmp(am, bm)
}

func cmp(a, b interface{}) error {
	switch a := a.(type) {
	case []interface{}:
		amap := make([]map[string]interface{}, len(a))
		for i, v := range a {
			amap[i], _ = v.(map[string]interface{})
		}

		switch b := b.(type) {
		case []interface{}:
			bmap := make([]map[string]interface{}, len(b))
			for i, v := range b {
				bmap[i], _ = v.(map[string]interface{})
			}
			var okcount int
			for _, av := range amap {
				for _, bv := range bmap {
					if cmpMap(av, bv) == nil {
						okcount++
						break
					}
				}
			}
			if okcount == len(b) {
				return nil
			}
			return fmt.Errorf("not all expected values were found: a = %v, b = %v", a, b)

		case map[string]interface{}:
			var err error
			for _, av := range amap {
				if err = cmpMap(av, b); err == nil {
					break
				}
			}
			return err
		}

	case map[string]interface{}:
		if b, ok := b.(map[string]interface{}); ok {
			return cmpMap(a, b)
		}
	}
	return fmt.Errorf("type mismatch, a = %T, b = %T", a, b)
}

func cmpMap(am, bm map[string]interface{}) error {
	for k, v := range bm {
		ov := am[k]
		switch ov := ov.(type) {
		case map[string]interface{}:
			if v, ok := v.(map[string]interface{}); ok {
				if err := cmpMap(ov, v); err != nil {
					return fmt.Errorf("%s: %v", k, err)
				}
			} else {
				return fmt.Errorf("%s: type mismatch %T vs %T", k, am[k], bm[k])
			}
		default:
			if !reflect.DeepEqual(v, ov) {
				return fmt.Errorf("%s wanted %v, got %v", k, v, ov)
			}
		}
	}
	return nil
}

func getVal(v interface{}) []byte {
	switch v := v.(type) {
	case []byte:
		return v
	case string:
		return []byte(v)
	case io.Reader:
		b, _ := ioutil.ReadAll(v)
		return b
	case nil:
		return nil
	}
	j, _ := json.Marshal(v)
	return j
}
<commit_msg>YAAF: Yet Another Array Fix<commit_after>package resty

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"reflect"
	"testing"
)

var (
	LogRequests = false
)

type TestRequest struct {
	Method string
	Path   string
	Data   interface{}

	ExpectedStatus int
	ExpectedData   interface{}
}

func (tr *TestRequest) String() string {
	return tr.Method + " " + tr.Path
}

func (tr *TestRequest) Run(t *testing.T, c *Client) {
	r := c.Do(tr.Method, tr.Path, tr.Data, nil)
	if LogRequests {
		t.Logf("%s: %s", tr.String(), r.Value)
	}

	switch {
	case r.Err != nil:
		t.Fatalf("%s: error: %v, status: %d, resp: %s", tr.String(), r.Err, r.Status, r.Value)
	case tr.ExpectedStatus == 0 && r.Status != 200, r.Status != tr.ExpectedStatus:
		t.Fatalf("%s: wanted %d, got %d: %s", tr.String(), tr.ExpectedStatus, r.Status, r.Value)
	case tr.ExpectedData != nil:
		if err := compareRes(r.Value, getVal(tr.ExpectedData)); err != nil {
			t.Fatalf("%s: %v", tr.String(), err)
		}
	}
}

// a == result, b == expected
func compareRes(a, b []byte) error {
	var am, bm interface{}
	if err := json.Unmarshal(a, &am); err != nil {
		return fmt.Errorf("%s: %v", a, err)
	}
	if err := json.Unmarshal(b, &bm); err != nil {
		return fmt.Errorf("%s: %v", b, err)
	}

	return cmp(am, bm)
}

func cmp(a, b interface{}) error {
	switch a := a.(type) {
	case []interface{}:
		amap := make([]map[string]interface{}, len(a))
		for i, v := range a {
			amap[i], _ = v.(map[string]interface{})
		}

		switch b := b.(type) {
		case []interface{}:
			bmap := make([]map[string]interface{}, len(b))
			for i, v := range b {
				bmap[i], _ = v.(map[string]interface{})
			}
			var okcount int
			for _, av := range amap {
				for _, bv := range bmap {
					if cmpMap(av, bv) == nil {
						okcount++
						break
					}
				}
			}
			if okcount == len(b) {
				return nil
			}
			return fmt.Errorf("not all expected values were found: a = %v, b = %v", a, b)

		case map[string]interface{}:
			var err error
			for _, av := range amap {
				if err = cmpMap(av, b); err == nil {
					break
				}
			}
			return err
		}

	case map[string]interface{}:
		if b, ok := b.(map[string]interface{}); ok {
			return cmpMap(a, b)
		}
	}
	return fmt.Errorf("type mismatch, a = %T, b = %T", a, b)
}

func cmpMap(am, bm map[string]interface{}) error {
	for k, v := range bm {
		ov := am[k]
		switch ov := ov.(type) {
		case map[string]interface{}:
			if v, ok := v.(map[string]interface{}); ok {
				if err := cmpMap(ov, v); err != nil {
					return fmt.Errorf("%s: %v", k, err)
				}
			} else {
				return fmt.Errorf("%s: type mismatch %T vs %T", k, am[k], bm[k])
			}
		case []interface{}:
			if err := cmp(ov, v); err != nil {
				return err
			}
		default:
			if !reflect.DeepEqual(v, ov) {
				return fmt.Errorf("%s wanted %v, got %v", k, v, ov)
			}
		}
	}
	return nil
}

func getVal(v interface{}) []byte {
	switch v := v.(type) {
	case []byte:
		return v
	case string:
		return []byte(v)
	case io.Reader:
		b, _ := ioutil.ReadAll(v)
		return b
	case nil:
		return nil
	}
	j, _ := json.Marshal(v)
	return j
}
<|endoftext|>


<commit_before>package oauth

import (
	"io/ioutil"
	"net/url"
	"path/filepath"
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

// FileSystemRepository represents a mongodb repository
type FileSystemRepository struct {
	sync.Mutex
	servers map[string]*OAuth
}

// NewFileSystemRepository creates a mongo OAuth Server repo
func NewFileSystemRepository(dir string) (*FileSystemRepository, error) {
	repo := &FileSystemRepository{servers: make(map[string]*OAuth)}
	// Grab json files from directory
	files, err := ioutil.ReadDir(dir)
	if nil != err {
		return nil, err
	}

	for _, f := range files {
		filePath := filepath.Join(dir, f.Name())
		definition := new(OAuth)

		v := viper.New()
		v.SetConfigFile(filePath)
		v.WatchConfig()
		v.OnConfigChange(func(e fsnotify.Event) {
			log.WithFields(log.Fields{"name": e.Name, "op": e.Op.String()}).Debug("OAauth2 configuration changed, reloading...")
			if err := v.Unmarshal(definition); err != nil {
				log.WithError(err).Error("Can't unmarshal the OAauth2 configuration")
			}
		})

		if err := v.ReadInConfig(); err != nil {
			log.WithError(err).Error("Couldn't load the OAauth2 definition file")
			return nil, err
		}

		if err := v.Unmarshal(definition); err != nil {
			return nil, err
		}

		if err = repo.Add(definition); err != nil {
			log.WithError(err).Error("Can't add the definition to the repository")
			return nil, err
		}
	}

	return repo, nil
}

// FindAll fetches all the OAuth Servers available
func (r *FileSystemRepository) FindAll() ([]*OAuth, error) {
	var servers []*OAuth
	for _, server := range r.servers {
		servers = append(servers, server)
	}

	return servers, nil
}

// FindByName find an OAuth Server by name
func (r *FileSystemRepository) FindByName(name string) (*OAuth, error) {
	server, ok := r.servers[name]
	if false == ok {
		return nil, ErrOauthServerNotFound
	}

	return server, nil
}

// Add adds an OAuth Server to the repository
func (r *FileSystemRepository) Add(server *OAuth) error {
	r.Lock()
	defer r.Unlock()

	r.servers[server.Name] = server

	return nil
}

// Remove removes an OAuth Server from the repository
func (r *FileSystemRepository) Remove(name string) error {
	r.Lock()
	defer r.Unlock()

	delete(r.servers, name)
	return nil
}

// FindByTokenURL returns OAuth Server records with corresponding token url
func (r *FileSystemRepository) FindByTokenURL(url url.URL) (*OAuth, error) {
	for _, server := range r.servers {
		if server.Endpoints.Token.UpstreamURL == url.String() {
			return server, nil
		}
	}

	return nil, ErrOauthServerNotFound
}
<commit_msg>Changed viper to normal json<commit_after>package oauth

import (
	"encoding/json"
	"io/ioutil"
	"net/url"
	"path/filepath"
	"strings"
	"sync"

	log "github.com/Sirupsen/logrus"
)

// FileSystemRepository represents a mongodb repository
type FileSystemRepository struct {
	sync.Mutex
	servers map[string]*OAuth
}

// NewFileSystemRepository creates a mongo OAuth Server repo
func NewFileSystemRepository(dir string) (*FileSystemRepository, error) {
	repo := &FileSystemRepository{servers: make(map[string]*OAuth)}
	// Grab json files from directory
	files, err := ioutil.ReadDir(dir)
	if nil != err {
		return nil, err
	}

	for _, f := range files {
		if strings.Contains(f.Name(), ".json") {
			filePath := filepath.Join(dir, f.Name())
			oauthServerRaw, err := ioutil.ReadFile(filePath)
			if err != nil {
				log.WithError(err).WithField("path", filePath).Error("Couldn't load the oauth server file")
				return nil, err
			}

			oauthServer := repo.parseOAuthServer(oauthServerRaw)
			if err = repo.Add(oauthServer); err != nil {
				log.WithError(err).Error("Can't add the definition to the repository")
				return nil, err
			}
		}
	}

	return repo, nil
}

// FindAll fetches all the OAuth Servers available
func (r *FileSystemRepository) FindAll() ([]*OAuth, error) {
	var servers []*OAuth
	for _, server := range r.servers {
		servers = append(servers, server)
	}

	return servers, nil
}

// FindByName find an OAuth Server by name
func (r *FileSystemRepository) FindByName(name string) (*OAuth, error) {
	server, ok := r.servers[name]
	if false == ok {
		return nil, ErrOauthServerNotFound
	}

	return server, nil
}

// Add adds an OAuth Server to the repository
func (r *FileSystemRepository) Add(server *OAuth) error {
	r.Lock()
	defer r.Unlock()

	r.servers[server.Name] = server

	return nil
}

// Remove removes an OAuth Server from the repository
func (r *FileSystemRepository) Remove(name string) error {
	r.Lock()
	defer r.Unlock()

	delete(r.servers, name)
	return nil
}

// FindByTokenURL returns OAuth Server records with corresponding token url
func (r *FileSystemRepository) FindByTokenURL(url url.URL) (*OAuth, error) {
	for _, server := range r.servers {
		if server.Endpoints.Token.UpstreamURL == url.String() {
			return server, nil
		}
	}

	return nil, ErrOauthServerNotFound
}

func (r *FileSystemRepository) parseOAuthServer(oauthServerRaw []byte) *OAuth {
	oauthServer := new(OAuth)
	if err := json.Unmarshal(oauthServerRaw, oauthServer); err != nil {
		log.WithError(err).Error("[RPC] --> Couldn't unmarshal oauth server configuration")
	}

	return oauthServer
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Rook Authors. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package nfs for NFS ganesha
package nfs

import (
	"fmt"

	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/clusterd"
	"github.com/rook/rook/pkg/operator/k8sutil"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Create the ganesha server
func (c *CephNFSController) createCephNFS(n cephv1.CephNFS) error {
	if err := validateGanesha(c.context, n); err != nil {
		return err
	}

	logger.Infof("start running ceph nfs %s", n.Name)

	for i := 0; i < n.Spec.Server.Active; i++ {
		name := k8sutil.IndexToName(i)

		configName, err := c.generateConfig(n, name)
		if err != nil {
			return fmt.Errorf("failed to create config. %+v", err)
		}

		err = c.addRADOSConfigFile(n, name)
		if err != nil {
			return fmt.Errorf("failed to create RADOS config object. %+v", err)
		}

		// start the deployment
		deployment := c.makeDeployment(n, name, configName)
		_, err = c.context.Clientset.ExtensionsV1beta1().Deployments(n.Namespace).Create(deployment)
		if err != nil {
			if !errors.IsAlreadyExists(err) {
				return fmt.Errorf("failed to create ganesha deployment. %+v", err)
			}
			logger.Infof("ganesha deployment %s already exists", deployment.Name)
		} else {
			logger.Infof("ganesha deployment %s started", deployment.Name)
		}

		// create a service
		err = c.createCephNFSService(n, name)
		if err != nil {
			return fmt.Errorf("failed to create ganesha service. %+v", err)
		}

		if err = c.addServerToDatabase(n, name); err != nil {
			logger.Warningf("Failed to add ganesha server %s to database. It may already be added. %+v", name, err)
		}
	}

	return nil
}

// Create empty config file for new ganesha server
func (c *CephNFSController) addRADOSConfigFile(n cephv1.CephNFS, name string) error {
	nodeID := getNFSNodeID(n, name)
	config := getGaneshaConfigObject(nodeID)
	err := c.context.Executor.ExecuteCommand(false, "", "rados", "--pool", n.Spec.RADOS.Pool, "--namespace", n.Spec.RADOS.Namespace, "stat", config)
	if err == nil {
		// If stat works then we assume it's present already
		return nil
	}
	// try to create it
	return c.context.Executor.ExecuteCommand(false, "", "rados", "--pool", n.Spec.RADOS.Pool, "--namespace", n.Spec.RADOS.Namespace, "create", config)
}

func (c *CephNFSController) addServerToDatabase(n cephv1.CephNFS, name string) error {
	nodeID := getNFSNodeID(n, name)
	logger.Infof("Adding ganesha %s to grace db", nodeID)
	return c.context.Executor.ExecuteCommand(false, "", "ganesha-rados-grace", "--pool", n.Spec.RADOS.Pool, "--ns", n.Spec.RADOS.Namespace, "add", nodeID)
}

func (c *CephNFSController) removeServerFromDatabase(n cephv1.CephNFS, name string) error {
	nodeID := getNFSNodeID(n, name)
	logger.Infof("Removing ganesha %s from grace db", nodeID)
	return c.context.Executor.ExecuteCommand(false, "", "ganesha-rados-grace", "--pool", n.Spec.RADOS.Pool, "--ns", n.Spec.RADOS.Namespace, "remove", nodeID)
}

func (c *CephNFSController) generateConfig(n cephv1.CephNFS, name string) (string, error) {

	data := map[string]string{
		"config": getGaneshaConfig(n, name),
	}
	configMap := &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%s-%s-%s", appName, n.Name, name),
			Namespace: n.Namespace,
			Labels:    getLabels(n, name),
		},
		Data: data,
	}
	if _, err := c.context.Clientset.CoreV1().ConfigMaps(n.Namespace).Create(configMap); err != nil {
		if errors.IsAlreadyExists(err) {
			if _, err := c.context.Clientset.CoreV1().ConfigMaps(n.Namespace).Update(configMap); err != nil {
				return "", fmt.Errorf("failed to update ganesha config. %+v", err)
			}
			return configMap.Name, nil
		}
		return "", fmt.Errorf("failed to create ganesha config. %+v", err)
	}
	return configMap.Name, nil
}

// Delete the ganesha server
func (c *CephNFSController) deleteGanesha(n cephv1.CephNFS) error {
	for i := 0; i < n.Spec.Server.Active; i++ {
		name := k8sutil.IndexToName(i)

		// Remove from grace db
		if err := c.removeServerFromDatabase(n, name); err != nil {
			logger.Warningf("failed to remove server %s from grace db. %+v", name, err)
		}

		// Delete the mds deployment
		k8sutil.DeleteDeployment(c.context.Clientset, n.Namespace, instanceName(n, name))

		// Delete the ganesha service
		options := &metav1.DeleteOptions{}
		err := c.context.Clientset.CoreV1().Services(n.Namespace).Delete(instanceName(n, name), options)
		if err != nil && !errors.IsNotFound(err) {
			logger.Warningf("failed to delete ganesha service. %+v", err)
		}
	}

	return nil
}

func instanceName(n cephv1.CephNFS, name string) string {
	return fmt.Sprintf("%s-%s-%s", appName, n.Name, name)
}

func validateGanesha(context *clusterd.Context, n cephv1.CephNFS) error {
	// core properties
	if n.Name == "" {
		return fmt.Errorf("missing name")
	}
	if n.Namespace == "" {
		return fmt.Errorf("missing namespace")
	}

	// Client recovery properties
	if n.Spec.RADOS.Pool == "" {
		return fmt.Errorf("missing RADOS.pool")
	}
	if n.Spec.RADOS.Namespace == "" {
		return fmt.Errorf("missing RADOS.namespace")
	}

	// Ganesha server properties
	if n.Spec.Server.Active == 0 {
		return fmt.Errorf("at least one active server required")
	}

	return nil
}
<commit_msg>nfs: add to servers to grace db with a job<commit_after>/*
Copyright 2018 The Rook Authors. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package nfs for NFS ganesha
package nfs

import (
	"fmt"
	"time"

	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/clusterd"
	opmon "github.com/rook/rook/pkg/operator/ceph/cluster/mon"
	opspec "github.com/rook/rook/pkg/operator/ceph/spec"
	"github.com/rook/rook/pkg/operator/k8sutil"
	batch "k8s.io/api/batch/v1"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	ganeshaRadosGraceCmd = "ganesha-rados-grace"
)

// Create the ganesha server
func (c *CephNFSController) createCephNFS(n cephv1.CephNFS) error {
	if err := validateGanesha(c.context, n); err != nil {
		return err
	}

	logger.Infof("start running ceph nfs %s", n.Name)

	for i := 0; i < n.Spec.Server.Active; i++ {
		name := k8sutil.IndexToName(i)

		configName, err := c.generateConfig(n, name)
		if err != nil {
			return fmt.Errorf("failed to create config. %+v", err)
		}

		err = c.addRADOSConfigFile(n, name)
		if err != nil {
			return fmt.Errorf("failed to create RADOS config object. %+v", err)
		}

		// start the deployment
		deployment := c.makeDeployment(n, name, configName)
		_, err = c.context.Clientset.ExtensionsV1beta1().Deployments(n.Namespace).Create(deployment)
		if err != nil {
			if !errors.IsAlreadyExists(err) {
				return fmt.Errorf("failed to create ganesha deployment. %+v", err)
			}
			logger.Infof("ganesha deployment %s already exists", deployment.Name)
		} else {
			logger.Infof("ganesha deployment %s started", deployment.Name)
		}

		// create a service
		err = c.createCephNFSService(n, name)
		if err != nil {
			return fmt.Errorf("failed to create ganesha service. %+v", err)
		}

		if err = c.addServerToDatabase(n, name); err != nil {
			logger.Warningf("Failed to add ganesha server %s to database. It may already be added. %+v", name, err)
		}
	}

	return nil
}

// Create empty config file for new ganesha server
func (c *CephNFSController) addRADOSConfigFile(n cephv1.CephNFS, name string) error {
	nodeID := getNFSNodeID(n, name)
	config := getGaneshaConfigObject(nodeID)
	err := c.context.Executor.ExecuteCommand(false, "", "rados", "--pool", n.Spec.RADOS.Pool, "--namespace", n.Spec.RADOS.Namespace, "stat", config)
	if err == nil {
		// If stat works then we assume it's present already
		return nil
	}
	// try to create it
	return c.context.Executor.ExecuteCommand(false, "", "rados", "--pool", n.Spec.RADOS.Pool, "--namespace", n.Spec.RADOS.Namespace, "create", config)
}

func (c *CephNFSController) addServerToDatabase(n cephv1.CephNFS, name string) error {
	logger.Infof("Adding ganesha %s to grace db", name)

	if err := c.runGaneshaRadosGraceJob(n, name, "add", 10*time.Minute); err != nil {
		logger.Errorf("failed to add %s to grace db. %+v", name, err)
	}
	return nil
}

func (c *CephNFSController) removeServerFromDatabase(n cephv1.CephNFS, name string) error {
	logger.Infof("Removing ganesha %s from grace db", name)

	if err := c.runGaneshaRadosGraceJob(n, name, "remove", 10*time.Minute); err != nil {
		logger.Errorf("failed to remmove %s from grace db. %+v", name, err)
	}
	return nil
}

func (c *CephNFSController) runGaneshaRadosGraceJob(n cephv1.CephNFS, name, action string, timeout time.Duration) error {
	nodeID := getNFSNodeID(n, name)
	args := []string{"--pool", n.Spec.RADOS.Pool, "--ns", n.Spec.RADOS.Namespace, action, nodeID}

	// FIX: After the operator is based on the nautilus image, we can execute the command directly instead of running a job
	//return c.context.Executor.ExecuteCommand(false, "", ganeshaRadosGraceCmd, args...)

	job := &batch.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "rook-ceph-nfs-ganesha-rados-grace",
			Namespace: n.Namespace,
		},
		Spec: batch.JobSpec{
			Template: v1.PodTemplateSpec{
				Spec: v1.PodSpec{
					InitContainers: []v1.Container{
						{
							Name: opspec.ConfigInitContainerName,
							Args: []string{
								"ceph",
								"config-init",
							},
							Image: k8sutil.MakeRookImage(c.rookImage),
							Env: []v1.EnvVar{
								{Name: "ROOK_USERNAME", Value: "client.admin"},
								{Name: "ROOK_KEYRING",
									ValueFrom: &v1.EnvVarSource{
										SecretKeyRef: &v1.SecretKeySelector{
											LocalObjectReference: v1.LocalObjectReference{Name: "rook-ceph-mon"},
											Key:                  "admin-secret",
										}}},
								k8sutil.PodIPEnvVar(k8sutil.PrivateIPEnvVar),
								k8sutil.PodIPEnvVar(k8sutil.PublicIPEnvVar),
								opmon.EndpointEnvVar(),
								k8sutil.ConfigOverrideEnvVar(),
							},
							VolumeMounts: opspec.RookVolumeMounts(),
						},
					},
					Containers: []v1.Container{
						{
							Command:      []string{ganeshaRadosGraceCmd},
							Args:         args,
							Name:         ganeshaRadosGraceCmd,
							Image:        c.cephVersion.Image,
							VolumeMounts: opspec.RookVolumeMounts(),
						},
					},
					Volumes:       opspec.PodVolumes(""),
					RestartPolicy: v1.RestartPolicyOnFailure,
				},
			},
		},
	}
	k8sutil.SetOwnerRef(c.context.Clientset, n.Namespace, &job.ObjectMeta, &c.ownerRef)

	// run the job to detect the version
	if err := k8sutil.RunReplaceableJob(c.context.Clientset, job); err != nil {
		return fmt.Errorf("failed to start job %s. %+v", job.Name, err)
	}

	if err := k8sutil.WaitForJobCompletion(c.context.Clientset, job, timeout); err != nil {
		return fmt.Errorf("failed to complete job %s. %+v", job.Name, err)
	}

	if err := k8sutil.DeleteBatchJob(c.context.Clientset, n.Namespace, job.Name, false); err != nil {
		return fmt.Errorf("failed to delete job %s. %+v", job.Name, err)
	}

	logger.Infof("successfully completed job %s", job.Name)
	return nil
}

func (c *CephNFSController) generateConfig(n cephv1.CephNFS, name string) (string, error) {

	data := map[string]string{
		"config": getGaneshaConfig(n, name),
	}
	configMap := &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%s-%s-%s", appName, n.Name, name),
			Namespace: n.Namespace,
			Labels:    getLabels(n, name),
		},
		Data: data,
	}
	if _, err := c.context.Clientset.CoreV1().ConfigMaps(n.Namespace).Create(configMap); err != nil {
		if errors.IsAlreadyExists(err) {
			if _, err := c.context.Clientset.CoreV1().ConfigMaps(n.Namespace).Update(configMap); err != nil {
				return "", fmt.Errorf("failed to update ganesha config. %+v", err)
			}
			return configMap.Name, nil
		}
		return "", fmt.Errorf("failed to create ganesha config. %+v", err)
	}
	return configMap.Name, nil
}

// Delete the ganesha server
func (c *CephNFSController) deleteGanesha(n cephv1.CephNFS) error {
	for i := 0; i < n.Spec.Server.Active; i++ {
		name := k8sutil.IndexToName(i)

		// Remove from grace db
		if err := c.removeServerFromDatabase(n, name); err != nil {
			logger.Warningf("failed to remove server %s from grace db. %+v", name, err)
		}

		// Delete the mds deployment
		k8sutil.DeleteDeployment(c.context.Clientset, n.Namespace, instanceName(n, name))

		// Delete the ganesha service
		options := &metav1.DeleteOptions{}
		err := c.context.Clientset.CoreV1().Services(n.Namespace).Delete(instanceName(n, name), options)
		if err != nil && !errors.IsNotFound(err) {
			logger.Warningf("failed to delete ganesha service. %+v", err)
		}
	}

	return nil
}

func instanceName(n cephv1.CephNFS, name string) string {
	return fmt.Sprintf("%s-%s-%s", appName, n.Name, name)
}

func validateGanesha(context *clusterd.Context, n cephv1.CephNFS) error {
	// core properties
	if n.Name == "" {
		return fmt.Errorf("missing name")
	}
	if n.Namespace == "" {
		return fmt.Errorf("missing namespace")
	}

	// Client recovery properties
	if n.Spec.RADOS.Pool == "" {
		return fmt.Errorf("missing RADOS.pool")
	}
	if n.Spec.RADOS.Namespace == "" {
		return fmt.Errorf("missing RADOS.namespace")
	}

	// Ganesha server properties
	if n.Spec.Server.Active == 0 {
		return fmt.Errorf("at least one active server required")
	}

	return nil
}
<|endoftext|>


<commit_before>package proxy

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/hellofresh/janus/pkg/middleware"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestDefinition(t *testing.T) {
	t.Parallel()

	tests := []struct {
		scenario string
		function func(*testing.T)
	}{
		{
			scenario: "new definitions",
			function: testNewDefinitions,
		},
		{
			scenario: "successful validation",
			function: testSuccessfulValidation,
		},
		{
			scenario: "empty listen path validation",
			function: testEmptyListenPathValidation,
		},
		{
			scenario: "invalid target url validation",
			function: testInvalidTargetURLValidation,
		},
		{
			scenario: "is balancer defined",
			function: testIsBalancerDefined,
		},
		{
			scenario: "add middleware",
			function: testAddMiddlewares,
		},
		{
			scenario: "marshal forwarding_timeouts to json",
			function: testMarshalForwardingTimeoutsToJSON,
		},
		{
			scenario: "unmarshal forwarding_timeouts from json",
			function: testUnmarshalForwardingTimeoutsFromJSON,
		},
	}

	for _, test := range tests {
		t.Run(test.scenario, func(t *testing.T) {
			test.function(t)
		})
	}
}

func testNewDefinitions(t *testing.T) {
	definition := NewDefinition()

	assert.Equal(t, []string{"GET"}, definition.Methods)
	assert.NotNil(t, definition)
}

func testSuccessfulValidation(t *testing.T) {
	definition := Definition{
		ListenPath: "/*",
		Upstreams: &Upstreams{
			Balancing: "roundrobin",
			Targets: Targets{
				{Target: "http://test.com"},
			},
		},
	}
	isValid, err := definition.Validate()

	assert.NoError(t, err)
	assert.True(t, isValid)
}

func testEmptyListenPathValidation(t *testing.T) {
	definition := Definition{}
	isValid, err := definition.Validate()

	assert.Error(t, err)
	assert.False(t, isValid)
}

func testInvalidTargetURLValidation(t *testing.T) {
	definition := Definition{
		ListenPath: " ",
		Upstreams: &Upstreams{
			Balancing: "roundrobin",
			Targets: Targets{
				{Target: "wrong"},
			},
		},
	}
	isValid, err := definition.Validate()

	assert.Error(t, err)
	assert.False(t, isValid)
}

func testIsBalancerDefined(t *testing.T) {
	definition := NewDefinition()
	assert.False(t, definition.IsBalancerDefined())

	target := &Target{Target: "http://localhost:8080/api-name"}
	definition.Upstreams.Targets = append(definition.Upstreams.Targets, target)
	assert.True(t, definition.IsBalancerDefined())
	assert.Len(t, definition.Upstreams.Targets.ToBalancerTargets(), 1)
}

func testAddMiddlewares(t *testing.T) {
	routerDefinition := NewRouterDefinition(NewDefinition())
	routerDefinition.AddMiddleware(middleware.NewLogger().Handler)

	assert.Len(t, routerDefinition.Middleware(), 1)
}

func testMarshalForwardingTimeoutsToJSON(t *testing.T) {
	definition := Definition{
		ListenPath: "/*",
		Upstreams: &Upstreams{
			Balancing: "roundrobin",
			Targets: Targets{
				{Target: "http://test.com"},
			},
		},
		ForwardingTimeouts: ForwardingTimeouts{
			DialTimeout:           Duration(30 * time.Second),
			ResponseHeaderTimeout: Duration(31 * time.Second),
		},
	}
	jsonDefinition, err := json.Marshal(&definition)
	require.NoError(t, err)
	assert.Contains(t, string(jsonDefinition), "30s")
	assert.Contains(t, string(jsonDefinition), "31s")
}

func testUnmarshalForwardingTimeoutsFromJSON(t *testing.T) {
	rawDefinition := []byte(`
  {
    "preserve_host":false,
    "listen_path":"/example/*",
    "upstreams":{
      "balancing":"roundrobin",
      "targets":[
        {
          "target":"http://localhost:9089/hello-world"
        }
      ]
    },
    "strip_path":false,
    "append_path":false,
    "methods":[
      "GET"
    ],
    "forwarding_timeouts": {
      "dial_timeout": "30s",
      "response_header_timeout": "31s"
    }
  }
`)
	definition := NewDefinition()
	err := json.Unmarshal(rawDefinition, &definition)
	require.NoError(t, err)

	assert.Equal(t, 30*time.Second, time.Duration(definition.ForwardingTimeouts.DialTimeout))
	assert.Equal(t, 31*time.Second, time.Duration(definition.ForwardingTimeouts.ResponseHeaderTimeout))
}
<commit_msg>Make JSONMarshalling assertion stricter<commit_after>package proxy

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/hellofresh/janus/pkg/middleware"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestDefinition(t *testing.T) {
	t.Parallel()

	tests := []struct {
		scenario string
		function func(*testing.T)
	}{
		{
			scenario: "new definitions",
			function: testNewDefinitions,
		},
		{
			scenario: "successful validation",
			function: testSuccessfulValidation,
		},
		{
			scenario: "empty listen path validation",
			function: testEmptyListenPathValidation,
		},
		{
			scenario: "invalid target url validation",
			function: testInvalidTargetURLValidation,
		},
		{
			scenario: "is balancer defined",
			function: testIsBalancerDefined,
		},
		{
			scenario: "add middleware",
			function: testAddMiddlewares,
		},
		{
			scenario: "marshal forwarding_timeouts to json",
			function: testMarshalForwardingTimeoutsToJSON,
		},
		{
			scenario: "unmarshal forwarding_timeouts from json",
			function: testUnmarshalForwardingTimeoutsFromJSON,
		},
	}

	for _, test := range tests {
		t.Run(test.scenario, func(t *testing.T) {
			test.function(t)
		})
	}
}

func testNewDefinitions(t *testing.T) {
	definition := NewDefinition()

	assert.Equal(t, []string{"GET"}, definition.Methods)
	assert.NotNil(t, definition)
}

func testSuccessfulValidation(t *testing.T) {
	definition := Definition{
		ListenPath: "/*",
		Upstreams: &Upstreams{
			Balancing: "roundrobin",
			Targets: Targets{
				{Target: "http://test.com"},
			},
		},
	}
	isValid, err := definition.Validate()

	assert.NoError(t, err)
	assert.True(t, isValid)
}

func testEmptyListenPathValidation(t *testing.T) {
	definition := Definition{}
	isValid, err := definition.Validate()

	assert.Error(t, err)
	assert.False(t, isValid)
}

func testInvalidTargetURLValidation(t *testing.T) {
	definition := Definition{
		ListenPath: " ",
		Upstreams: &Upstreams{
			Balancing: "roundrobin",
			Targets: Targets{
				{Target: "wrong"},
			},
		},
	}
	isValid, err := definition.Validate()

	assert.Error(t, err)
	assert.False(t, isValid)
}

func testIsBalancerDefined(t *testing.T) {
	definition := NewDefinition()
	assert.False(t, definition.IsBalancerDefined())

	target := &Target{Target: "http://localhost:8080/api-name"}
	definition.Upstreams.Targets = append(definition.Upstreams.Targets, target)
	assert.True(t, definition.IsBalancerDefined())
	assert.Len(t, definition.Upstreams.Targets.ToBalancerTargets(), 1)
}

func testAddMiddlewares(t *testing.T) {
	routerDefinition := NewRouterDefinition(NewDefinition())
	routerDefinition.AddMiddleware(middleware.NewLogger().Handler)

	assert.Len(t, routerDefinition.Middleware(), 1)
}

func testMarshalForwardingTimeoutsToJSON(t *testing.T) {
	definition := Definition{
		ListenPath: "/*",
		Upstreams: &Upstreams{
			Balancing: "roundrobin",
			Targets: Targets{
				{Target: "http://test.com"},
			},
		},
		ForwardingTimeouts: ForwardingTimeouts{
			DialTimeout:           Duration(30 * time.Second),
			ResponseHeaderTimeout: Duration(31 * time.Second),
		},
	}
	jsonDefinition, err := json.Marshal(&definition)
	require.NoError(t, err)
	assert.Contains(t, string(jsonDefinition), `"dial_timeout":"30s"`)
	assert.Contains(t, string(jsonDefinition), `"response_header_timeout":"31s"`)
}

func testUnmarshalForwardingTimeoutsFromJSON(t *testing.T) {
	rawDefinition := []byte(`
  {
    "preserve_host":false,
    "listen_path":"/example/*",
    "upstreams":{
      "balancing":"roundrobin",
      "targets":[
        {
          "target":"http://localhost:9089/hello-world"
        }
      ]
    },
    "strip_path":false,
    "append_path":false,
    "methods":[
      "GET"
    ],
    "forwarding_timeouts": {
      "dial_timeout": "30s",
      "response_header_timeout": "31s"
    }
  }
`)
	definition := NewDefinition()
	err := json.Unmarshal(rawDefinition, &definition)
	require.NoError(t, err)

	assert.Equal(t, 30*time.Second, time.Duration(definition.ForwardingTimeouts.DialTimeout))
	assert.Equal(t, 31*time.Second, time.Duration(definition.ForwardingTimeouts.ResponseHeaderTimeout))
}
<|endoftext|>