<commit_before>package scs

import (
	"bufio"
	"bytes"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/alexedwards/scs/v2/memstore"
)

// Deprecated: Session is a backwards-compatible alias for SessionManager.
type Session = SessionManager

// SessionManager holds the configuration settings for your sessions.
type SessionManager struct {
	// IdleTimeout controls the maximum length of time a session can be inactive
	// before it expires. For example, some applications may wish to set this so
	// there is a timeout after 20 minutes of inactivity. By default IdleTimeout
	// is not set and there is no inactivity timeout.
	IdleTimeout time.Duration

	// Lifetime controls the maximum length of time that a session is valid for
	// before it expires. The lifetime is an 'absolute expiry' which is set when
	// the session is first created and does not change. The default value is 24
	// hours.
	Lifetime time.Duration

	// Store controls the session store where the session data is persisted.
	Store Store

	// Cookie contains the configuration settings for session cookies.
	Cookie SessionCookie

	// Codec controls the encoder/decoder used to transform session data to a
	// byte slice for use by the session store. By default session data is
	// encoded/decoded using encoding/gob.
	Codec Codec

	// ErrorFunc allows you to control behavior when an error is encountered by
	// the LoadAndSave middleware. The default behavior is for a HTTP 500
	// "Internal Server Error" message to be sent to the client and the error
	// logged using Go's standard logger. If a custom ErrorFunc is set, then
	// control will be passed to this instead. A typical use would be to provide
	// a function which logs the error and returns a customized HTML error page.
	ErrorFunc func(http.ResponseWriter, *http.Request, error)

	// contextKey is the key used to set and retrieve the session data from a
	// context.Context. It's automatically generated to ensure uniqueness.
	contextKey contextKey
}

// SessionCookie contains the configuration settings for session cookies.
type SessionCookie struct {
	// Name sets the name of the session cookie. It should not contain
	// whitespace, commas, colons, semicolons, backslashes, the equals sign or
	// control characters as per RFC6265. The default cookie name is "session".
	// If your application uses two different sessions, you must make sure that
	// the cookie name for each is unique.
	Name string

	// Domain sets the 'Domain' attribute on the session cookie. By default
	// it will be set to the domain name that the cookie was issued from.
	Domain string

	// HttpOnly sets the 'HttpOnly' attribute on the session cookie. The
	// default value is true.
	HttpOnly bool

	// Path sets the 'Path' attribute on the session cookie. The default value
	// is "/". Passing the empty string "" will result in it being set to the
	// path that the cookie was issued from.
	Path string

	// Persist sets whether the session cookie should be persistent or not
	// (i.e. whether it should be retained after a user closes their browser).
	// The default value is true, which means that the session cookie will not
	// be destroyed when the user closes their browser and the appropriate
	// 'Expires' and 'MaxAge' values will be added to the session cookie.
	Persist bool

	// SameSite controls the value of the 'SameSite' attribute on the session
	// cookie. By default this is set to 'SameSite=Lax'. If you want no SameSite
	// attribute or value in the session cookie then you should set this to 0.
	SameSite http.SameSite

	// Secure sets the 'Secure' attribute on the session cookie. The default
	// value is false. It's recommended that you set this to true and serve all
	// requests over HTTPS in production environments.
	// See https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Session_Management_Cheat_Sheet.md#transport-layer-security.
	Secure bool
}

// New returns a new session manager with the default options. It is safe for
// concurrent use.
func New() *SessionManager {
	s := &SessionManager{
		IdleTimeout: 0,
		Lifetime:    24 * time.Hour,
		Store:       memstore.New(),
		Codec:       GobCodec{},
		ErrorFunc:   defaultErrorFunc,
		contextKey:  generateContextKey(),
		Cookie: SessionCookie{
			Name:     "session",
			Domain:   "",
			HttpOnly: true,
			Path:     "/",
			Persist:  true,
			Secure:   false,
			SameSite: http.SameSiteLaxMode,
		},
	}
	return s
}

// Deprecated: NewSession is a backwards-compatible alias for New. Use the New
// function instead.
func NewSession() *SessionManager {
	return New()
}

// LoadAndSave provides middleware which automatically loads and saves session
// data for the current request, and communicates the session token to and from
// the client in a cookie.
func (s *SessionManager) LoadAndSave(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var token string
		cookie, err := r.Cookie(s.Cookie.Name)
		if err == nil {
			token = cookie.Value
		}

		ctx, err := s.Load(r.Context(), token)
		if err != nil {
			s.ErrorFunc(w, r, err)
			return
		}

		sr := r.WithContext(ctx)
		bw := &bufferedResponseWriter{ResponseWriter: w}
		next.ServeHTTP(bw, sr)

		if sr.MultipartForm != nil {
			sr.MultipartForm.RemoveAll()
		}

		switch s.Status(ctx) {
		case Modified:
			token, expiry, err := s.Commit(ctx)
			if err != nil {
				s.ErrorFunc(w, r, err)
				return
			}
			s.WriteSessionCookie(w, token, expiry)
		case Destroyed:
			s.WriteSessionCookie(w, "", time.Time{})
		}

		if bw.code != 0 {
			w.WriteHeader(bw.code)
		}
		w.Write(bw.buf.Bytes())
	})
}

// WriteSessionCookie writes a cookie with the provided session token and
// expiry time.
//
// Most applications will use the LoadAndSave() middleware and will not need to
// use this method.
func (s *SessionManager) WriteSessionCookie(w http.ResponseWriter, token string, expiry time.Time) {
	cookie := &http.Cookie{
		Name:     s.Cookie.Name,
		Value:    token,
		Path:     s.Cookie.Path,
		Domain:   s.Cookie.Domain,
		Secure:   s.Cookie.Secure,
		HttpOnly: s.Cookie.HttpOnly,
		SameSite: s.Cookie.SameSite,
	}

	if expiry.IsZero() {
		cookie.Expires = time.Unix(1, 0)
		cookie.MaxAge = -1
	} else if s.Cookie.Persist {
		cookie.Expires = time.Unix(expiry.Unix()+1, 0)        // Round up to the nearest second.
		cookie.MaxAge = int(time.Until(expiry).Seconds() + 1) // Round up to the nearest second.
	}

	w.Header().Add("Set-Cookie", cookie.String())
	addHeaderIfMissing(w, "Cache-Control", `no-cache="Set-Cookie"`)
	addHeaderIfMissing(w, "Vary", "Cookie")

}

func addHeaderIfMissing(w http.ResponseWriter, key, value string) {
	for _, h := range w.Header()[key] {
		if h == value {
			return
		}
	}
	w.Header().Add(key, value)
}

func defaultErrorFunc(w http.ResponseWriter, r *http.Request, err error) {
	log.Output(2, err.Error())
	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

type bufferedResponseWriter struct {
	http.ResponseWriter
	buf         bytes.Buffer
	code        int
	wroteHeader bool
}

func (bw *bufferedResponseWriter) Write(b []byte) (int, error) {
	return bw.buf.Write(b)
}

func (bw *bufferedResponseWriter) WriteHeader(code int) {
	if !bw.wroteHeader {
		bw.code = code
		bw.wroteHeader = true
	}
}

func (bw *bufferedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hj := bw.ResponseWriter.(http.Hijacker)
	return hj.Hijack()
}

func (bw *bufferedResponseWriter) Push(target string, opts *http.PushOptions) error {
	if pusher, ok := bw.ResponseWriter.(http.Pusher); ok {
		return pusher.Push(target, opts)
	}
	return http.ErrNotSupported
}
<commit_msg>Unexport experimental writeSessionCookie method<commit_after>package scs

import (
	"bufio"
	"bytes"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/alexedwards/scs/v2/memstore"
)

// Deprecated: Session is a backwards-compatible alias for SessionManager.
type Session = SessionManager

// SessionManager holds the configuration settings for your sessions.
type SessionManager struct {
	// IdleTimeout controls the maximum length of time a session can be inactive
	// before it expires. For example, some applications may wish to set this so
	// there is a timeout after 20 minutes of inactivity. By default IdleTimeout
	// is not set and there is no inactivity timeout.
	IdleTimeout time.Duration

	// Lifetime controls the maximum length of time that a session is valid for
	// before it expires. The lifetime is an 'absolute expiry' which is set when
	// the session is first created and does not change. The default value is 24
	// hours.
	Lifetime time.Duration

	// Store controls the session store where the session data is persisted.
	Store Store

	// Cookie contains the configuration settings for session cookies.
	Cookie SessionCookie

	// Codec controls the encoder/decoder used to transform session data to a
	// byte slice for use by the session store. By default session data is
	// encoded/decoded using encoding/gob.
	Codec Codec

	// ErrorFunc allows you to control behavior when an error is encountered by
	// the LoadAndSave middleware. The default behavior is for a HTTP 500
	// "Internal Server Error" message to be sent to the client and the error
	// logged using Go's standard logger. If a custom ErrorFunc is set, then
	// control will be passed to this instead. A typical use would be to provide
	// a function which logs the error and returns a customized HTML error page.
	ErrorFunc func(http.ResponseWriter, *http.Request, error)

	// contextKey is the key used to set and retrieve the session data from a
	// context.Context. It's automatically generated to ensure uniqueness.
	contextKey contextKey
}

// SessionCookie contains the configuration settings for session cookies.
type SessionCookie struct {
	// Name sets the name of the session cookie. It should not contain
	// whitespace, commas, colons, semicolons, backslashes, the equals sign or
	// control characters as per RFC6265. The default cookie name is "session".
	// If your application uses two different sessions, you must make sure that
	// the cookie name for each is unique.
	Name string

	// Domain sets the 'Domain' attribute on the session cookie. By default
	// it will be set to the domain name that the cookie was issued from.
	Domain string

	// HttpOnly sets the 'HttpOnly' attribute on the session cookie. The
	// default value is true.
	HttpOnly bool

	// Path sets the 'Path' attribute on the session cookie. The default value
	// is "/". Passing the empty string "" will result in it being set to the
	// path that the cookie was issued from.
	Path string

	// Persist sets whether the session cookie should be persistent or not
	// (i.e. whether it should be retained after a user closes their browser).
	// The default value is true, which means that the session cookie will not
	// be destroyed when the user closes their browser and the appropriate
	// 'Expires' and 'MaxAge' values will be added to the session cookie.
	Persist bool

	// SameSite controls the value of the 'SameSite' attribute on the session
	// cookie. By default this is set to 'SameSite=Lax'. If you want no SameSite
	// attribute or value in the session cookie then you should set this to 0.
	SameSite http.SameSite

	// Secure sets the 'Secure' attribute on the session cookie. The default
	// value is false. It's recommended that you set this to true and serve all
	// requests over HTTPS in production environments.
	// See https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Session_Management_Cheat_Sheet.md#transport-layer-security.
	Secure bool
}

// New returns a new session manager with the default options. It is safe for
// concurrent use.
func New() *SessionManager {
	s := &SessionManager{
		IdleTimeout: 0,
		Lifetime:    24 * time.Hour,
		Store:       memstore.New(),
		Codec:       GobCodec{},
		ErrorFunc:   defaultErrorFunc,
		contextKey:  generateContextKey(),
		Cookie: SessionCookie{
			Name:     "session",
			Domain:   "",
			HttpOnly: true,
			Path:     "/",
			Persist:  true,
			Secure:   false,
			SameSite: http.SameSiteLaxMode,
		},
	}
	return s
}

// Deprecated: NewSession is a backwards-compatible alias for New. Use the New
// function instead.
func NewSession() *SessionManager {
	return New()
}

// LoadAndSave provides middleware which automatically loads and saves session
// data for the current request, and communicates the session token to and from
// the client in a cookie.
func (s *SessionManager) LoadAndSave(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var token string
		cookie, err := r.Cookie(s.Cookie.Name)
		if err == nil {
			token = cookie.Value
		}

		ctx, err := s.Load(r.Context(), token)
		if err != nil {
			s.ErrorFunc(w, r, err)
			return
		}

		sr := r.WithContext(ctx)
		bw := &bufferedResponseWriter{ResponseWriter: w}
		next.ServeHTTP(bw, sr)

		if sr.MultipartForm != nil {
			sr.MultipartForm.RemoveAll()
		}

		switch s.Status(ctx) {
		case Modified:
			token, expiry, err := s.Commit(ctx)
			if err != nil {
				s.ErrorFunc(w, r, err)
				return
			}
			s.writeSessionCookie(w, token, expiry)
		case Destroyed:
			s.writeSessionCookie(w, "", time.Time{})
		}

		if bw.code != 0 {
			w.WriteHeader(bw.code)
		}
		w.Write(bw.buf.Bytes())
	})
}

func (s *SessionManager) writeSessionCookie(w http.ResponseWriter, token string, expiry time.Time) {
	cookie := &http.Cookie{
		Name:     s.Cookie.Name,
		Value:    token,
		Path:     s.Cookie.Path,
		Domain:   s.Cookie.Domain,
		Secure:   s.Cookie.Secure,
		HttpOnly: s.Cookie.HttpOnly,
		SameSite: s.Cookie.SameSite,
	}

	if expiry.IsZero() {
		cookie.Expires = time.Unix(1, 0)
		cookie.MaxAge = -1
	} else if s.Cookie.Persist {
		cookie.Expires = time.Unix(expiry.Unix()+1, 0)        // Round up to the nearest second.
		cookie.MaxAge = int(time.Until(expiry).Seconds() + 1) // Round up to the nearest second.
	}

	w.Header().Add("Set-Cookie", cookie.String())
	addHeaderIfMissing(w, "Cache-Control", `no-cache="Set-Cookie"`)
	addHeaderIfMissing(w, "Vary", "Cookie")

}

func addHeaderIfMissing(w http.ResponseWriter, key, value string) {
	for _, h := range w.Header()[key] {
		if h == value {
			return
		}
	}
	w.Header().Add(key, value)
}

func defaultErrorFunc(w http.ResponseWriter, r *http.Request, err error) {
	log.Output(2, err.Error())
	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

type bufferedResponseWriter struct {
	http.ResponseWriter
	buf         bytes.Buffer
	code        int
	wroteHeader bool
}

func (bw *bufferedResponseWriter) Write(b []byte) (int, error) {
	return bw.buf.Write(b)
}

func (bw *bufferedResponseWriter) WriteHeader(code int) {
	if !bw.wroteHeader {
		bw.code = code
		bw.wroteHeader = true
	}
}

func (bw *bufferedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hj := bw.ResponseWriter.(http.Hijacker)
	return hj.Hijack()
}

func (bw *bufferedResponseWriter) Push(target string, opts *http.PushOptions) error {
	if pusher, ok := bw.ResponseWriter.(http.Pusher); ok {
		return pusher.Push(target, opts)
	}
	return http.ErrNotSupported
}
<|endoftext|>


<commit_before>// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestRepositoriesService_CreateHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	input := &Hook{CreatedAt: &referenceTime}

	mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) {
		v := new(createHookRequest)
		json.NewDecoder(r.Body).Decode(v)

		testMethod(t, r, "POST")
		want := &createHookRequest{Name: "web"}
		if !cmp.Equal(v, want) {
			t.Errorf("Request body = %+v, want %+v", v, want)
		}

		fmt.Fprint(w, `{"id":1}`)
	})

	ctx := context.Background()
	hook, _, err := client.Repositories.CreateHook(ctx, "o", "r", input)
	if err != nil {
		t.Errorf("Repositories.CreateHook returned error: %v", err)
	}

	want := &Hook{ID: Int64(1)}
	if !cmp.Equal(hook, want) {
		t.Errorf("Repositories.CreateHook returned %+v, want %+v", hook, want)
	}

	const methodName = "CreateHook"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.CreateHook(ctx, "\n", "\n", input)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.CreateHook(ctx, "o", "r", input)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_ListHooks(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"page": "2"})
		fmt.Fprint(w, `[{"id":1}, {"id":2}]`)
	})

	opt := &ListOptions{Page: 2}

	ctx := context.Background()
	hooks, _, err := client.Repositories.ListHooks(ctx, "o", "r", opt)
	if err != nil {
		t.Errorf("Repositories.ListHooks returned error: %v", err)
	}

	want := []*Hook{{ID: Int64(1)}, {ID: Int64(2)}}
	if !cmp.Equal(hooks, want) {
		t.Errorf("Repositories.ListHooks returned %+v, want %+v", hooks, want)
	}

	const methodName = "ListHooks"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.ListHooks(ctx, "\n", "\n", opt)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.ListHooks(ctx, "o", "r", opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_ListHooks_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.ListHooks(ctx, "%", "%", nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_GetHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":1}`)
	})

	ctx := context.Background()
	hook, _, err := client.Repositories.GetHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.GetHook returned error: %v", err)
	}

	want := &Hook{ID: Int64(1)}
	if !cmp.Equal(hook, want) {
		t.Errorf("Repositories.GetHook returned %+v, want %+v", hook, want)
	}

	const methodName = "GetHook"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.GetHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.GetHook(ctx, "o", "r", 1)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_GetHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.GetHook(ctx, "%", "%", 1)
	testURLParseError(t, err)
}

func TestRepositoriesService_EditHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	input := &Hook{}

	mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) {
		v := new(Hook)
		json.NewDecoder(r.Body).Decode(v)

		testMethod(t, r, "PATCH")
		if !cmp.Equal(v, input) {
			t.Errorf("Request body = %+v, want %+v", v, input)
		}

		fmt.Fprint(w, `{"id":1}`)
	})

	ctx := context.Background()
	hook, _, err := client.Repositories.EditHook(ctx, "o", "r", 1, input)
	if err != nil {
		t.Errorf("Repositories.EditHook returned error: %v", err)
	}

	want := &Hook{ID: Int64(1)}
	if !cmp.Equal(hook, want) {
		t.Errorf("Repositories.EditHook returned %+v, want %+v", hook, want)
	}

	const methodName = "EditHook"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.EditHook(ctx, "\n", "\n", -1, input)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.EditHook(ctx, "o", "r", 1, input)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_EditHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.EditHook(ctx, "%", "%", 1, nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_DeleteHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
	})

	ctx := context.Background()
	_, err := client.Repositories.DeleteHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.DeleteHook returned error: %v", err)
	}

	const methodName = "DeleteHook"
	testBadOptions(t, methodName, func() (err error) {
		_, err = client.Repositories.DeleteHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		return client.Repositories.DeleteHook(ctx, "o", "r", 1)
	})
}

func TestRepositoriesService_DeleteHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, err := client.Repositories.DeleteHook(ctx, "%", "%", 1)
	testURLParseError(t, err)
}

func TestRepositoriesService_PingHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
	})

	ctx := context.Background()
	_, err := client.Repositories.PingHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.PingHook returned error: %v", err)
	}

	const methodName = "PingHook"
	testBadOptions(t, methodName, func() (err error) {
		_, err = client.Repositories.PingHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		return client.Repositories.PingHook(ctx, "o", "r", 1)
	})
}

func TestRepositoriesService_TestHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1/tests", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
	})

	ctx := context.Background()
	_, err := client.Repositories.TestHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.TestHook returned error: %v", err)
	}

	const methodName = "TestHook"
	testBadOptions(t, methodName, func() (err error) {
		_, err = client.Repositories.TestHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		return client.Repositories.TestHook(ctx, "o", "r", 1)
	})
}

func TestRepositoriesService_TestHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, err := client.Repositories.TestHook(ctx, "%", "%", 1)
	testURLParseError(t, err)
}
<commit_msg>Add test cases for JSON resources marshaling (#2079)<commit_after>// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestRepositoriesService_CreateHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	input := &Hook{CreatedAt: &referenceTime}

	mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) {
		v := new(createHookRequest)
		json.NewDecoder(r.Body).Decode(v)

		testMethod(t, r, "POST")
		want := &createHookRequest{Name: "web"}
		if !cmp.Equal(v, want) {
			t.Errorf("Request body = %+v, want %+v", v, want)
		}

		fmt.Fprint(w, `{"id":1}`)
	})

	ctx := context.Background()
	hook, _, err := client.Repositories.CreateHook(ctx, "o", "r", input)
	if err != nil {
		t.Errorf("Repositories.CreateHook returned error: %v", err)
	}

	want := &Hook{ID: Int64(1)}
	if !cmp.Equal(hook, want) {
		t.Errorf("Repositories.CreateHook returned %+v, want %+v", hook, want)
	}

	const methodName = "CreateHook"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.CreateHook(ctx, "\n", "\n", input)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.CreateHook(ctx, "o", "r", input)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_ListHooks(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"page": "2"})
		fmt.Fprint(w, `[{"id":1}, {"id":2}]`)
	})

	opt := &ListOptions{Page: 2}

	ctx := context.Background()
	hooks, _, err := client.Repositories.ListHooks(ctx, "o", "r", opt)
	if err != nil {
		t.Errorf("Repositories.ListHooks returned error: %v", err)
	}

	want := []*Hook{{ID: Int64(1)}, {ID: Int64(2)}}
	if !cmp.Equal(hooks, want) {
		t.Errorf("Repositories.ListHooks returned %+v, want %+v", hooks, want)
	}

	const methodName = "ListHooks"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.ListHooks(ctx, "\n", "\n", opt)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.ListHooks(ctx, "o", "r", opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_ListHooks_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.ListHooks(ctx, "%", "%", nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_GetHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":1}`)
	})

	ctx := context.Background()
	hook, _, err := client.Repositories.GetHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.GetHook returned error: %v", err)
	}

	want := &Hook{ID: Int64(1)}
	if !cmp.Equal(hook, want) {
		t.Errorf("Repositories.GetHook returned %+v, want %+v", hook, want)
	}

	const methodName = "GetHook"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.GetHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.GetHook(ctx, "o", "r", 1)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_GetHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.GetHook(ctx, "%", "%", 1)
	testURLParseError(t, err)
}

func TestRepositoriesService_EditHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	input := &Hook{}

	mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) {
		v := new(Hook)
		json.NewDecoder(r.Body).Decode(v)

		testMethod(t, r, "PATCH")
		if !cmp.Equal(v, input) {
			t.Errorf("Request body = %+v, want %+v", v, input)
		}

		fmt.Fprint(w, `{"id":1}`)
	})

	ctx := context.Background()
	hook, _, err := client.Repositories.EditHook(ctx, "o", "r", 1, input)
	if err != nil {
		t.Errorf("Repositories.EditHook returned error: %v", err)
	}

	want := &Hook{ID: Int64(1)}
	if !cmp.Equal(hook, want) {
		t.Errorf("Repositories.EditHook returned %+v, want %+v", hook, want)
	}

	const methodName = "EditHook"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.EditHook(ctx, "\n", "\n", -1, input)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.EditHook(ctx, "o", "r", 1, input)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_EditHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.EditHook(ctx, "%", "%", 1, nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_DeleteHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
	})

	ctx := context.Background()
	_, err := client.Repositories.DeleteHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.DeleteHook returned error: %v", err)
	}

	const methodName = "DeleteHook"
	testBadOptions(t, methodName, func() (err error) {
		_, err = client.Repositories.DeleteHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		return client.Repositories.DeleteHook(ctx, "o", "r", 1)
	})
}

func TestRepositoriesService_DeleteHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, err := client.Repositories.DeleteHook(ctx, "%", "%", 1)
	testURLParseError(t, err)
}

func TestRepositoriesService_PingHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
	})

	ctx := context.Background()
	_, err := client.Repositories.PingHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.PingHook returned error: %v", err)
	}

	const methodName = "PingHook"
	testBadOptions(t, methodName, func() (err error) {
		_, err = client.Repositories.PingHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		return client.Repositories.PingHook(ctx, "o", "r", 1)
	})
}

func TestRepositoriesService_TestHook(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/hooks/1/tests", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
	})

	ctx := context.Background()
	_, err := client.Repositories.TestHook(ctx, "o", "r", 1)
	if err != nil {
		t.Errorf("Repositories.TestHook returned error: %v", err)
	}

	const methodName = "TestHook"
	testBadOptions(t, methodName, func() (err error) {
		_, err = client.Repositories.TestHook(ctx, "\n", "\n", -1)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		return client.Repositories.TestHook(ctx, "o", "r", 1)
	})
}

func TestRepositoriesService_TestHook_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, err := client.Repositories.TestHook(ctx, "%", "%", 1)
	testURLParseError(t, err)
}

func TestBranchWebHookPayload_Marshal(t *testing.T) {
	testJSONMarshal(t, &WebHookPayload{}, "{}")

	v := &WebHookPayload{
		Action: String("action"),
		After:  String("after"),
		Before: String("before"),
		Commits: []*WebHookCommit{
			{
				Added: []string{"1", "2", "3"},
				Author: &WebHookAuthor{
					Email:    String("abc@gmail.com"),
					Name:     String("abc"),
					Username: String("abc_12"),
				},
				Committer: &WebHookAuthor{
					Email:    String("abc@gmail.com"),
					Name:     String("abc"),
					Username: String("abc_12"),
				},
				ID:       String("1"),
				Message:  String("WebHookCommit"),
				Modified: []string{"abc", "efg", "erd"},
				Removed:  []string{"cmd", "rti", "duv"},
			},
		},
		Compare: String("compare"),
		Created: Bool(true),
		Forced:  Bool(false),
		HeadCommit: &WebHookCommit{
			Added: []string{"1", "2", "3"},
			Author: &WebHookAuthor{
				Email:    String("abc@gmail.com"),
				Name:     String("abc"),
				Username: String("abc_12"),
			},
			Committer: &WebHookAuthor{
				Email:    String("abc@gmail.com"),
				Name:     String("abc"),
				Username: String("abc_12"),
			},
			ID:       String("1"),
			Message:  String("WebHookCommit"),
			Modified: []string{"abc", "efg", "erd"},
			Removed:  []string{"cmd", "rti", "duv"},
		},
		Installation: &Installation{
			ID: Int64(12),
		},
		Organization: &Organization{
			ID: Int64(22),
		},
		Pusher: &User{
			Login: String("rd@yahoo.com"),
			ID:    Int64(112),
		},
		Repo: &Repository{
			ID:     Int64(321),
			NodeID: String("node_321"),
		},
		Sender: &User{
			Login: String("st@gmail.com"),
			ID:    Int64(202),
		},
	}

	want := `{
		"action": "action",
		"after":  "after",
		"before": "before",
		"commits": [
			{
			"added":   ["1", "2", "3"],
			"author":{
				"email": "abc@gmail.com",
				"name": "abc",
				"username": "abc_12"
			},
			"committer": {
				"email": "abc@gmail.com",
				"name": "abc",
				"username": "abc_12"
			},
			"id":       "1",
			"message":  "WebHookCommit",
			"modified": ["abc", "efg", "erd"],
			"removed":  ["cmd", "rti", "duv"]
			}
		],
		"compare": "compare",
		"created": true,
		"forced":  false,
		"head_commit": {
			"added":   ["1", "2", "3"],
		"author":{
			"email": "abc@gmail.com",
			"name": "abc",
			"username": "abc_12"
		},
		"committer": {
			"email": "abc@gmail.com",
			"name": "abc",
			"username": "abc_12"
		},
		"id":       "1",
		"message":  "WebHookCommit",
		"modified": ["abc", "efg", "erd"],
		"removed":  ["cmd", "rti", "duv"]
		},
		"installation": {
			"id": 12
		},
		"organization": {
			"id" : 22
		},
		"pusher":{
			"login": "rd@yahoo.com",
			"id": 112
		},
		"repository":{
			"id": 321,
			"node_id": "node_321"
		},
		"sender":{
			"login": "st@gmail.com",
			"id": 202
		}
	}`

	testJSONMarshal(t, v, want)
}

func TestBranchWebHookAuthor_Marshal(t *testing.T) {
	testJSONMarshal(t, &WebHookAuthor{}, "{}")

	v := &WebHookAuthor{
		Email:    String("abc@gmail.com"),
		Name:     String("abc"),
		Username: String("abc_12"),
	}

	want := `{
			"email": "abc@gmail.com",
			"name": "abc",
			"username": "abc_12"
	}`

	testJSONMarshal(t, v, want)
}

func TestBranchWebHookCommit_Marshal(t *testing.T) {
	testJSONMarshal(t, &WebHookCommit{}, "{}")

	v := &WebHookCommit{
		Added: []string{"1", "2", "3"},
		Author: &WebHookAuthor{
			Email:    String("abc@gmail.com"),
			Name:     String("abc"),
			Username: String("abc_12"),
		},
		Committer: &WebHookAuthor{
			Email:    String("abc@gmail.com"),
			Name:     String("abc"),
			Username: String("abc_12"),
		},
		ID:       String("1"),
		Message:  String("WebHookCommit"),
		Modified: []string{"abc", "efg", "erd"},
		Removed:  []string{"cmd", "rti", "duv"},
	}

	want := `{
		"added":   ["1", "2", "3"],
		"author":{
			"email": "abc@gmail.com",
			"name": "abc",
			"username": "abc_12"
		},
		"committer": {
			"email": "abc@gmail.com",
			"name": "abc",
			"username": "abc_12"
		},
		"id":       "1",
		"message":  "WebHookCommit",
		"modified": ["abc", "efg", "erd"],
		"removed":  ["cmd", "rti", "duv"]
	}`

	testJSONMarshal(t, v, want)
}

func TestBranchCreateHookRequest_Marshal(t *testing.T) {
	testJSONMarshal(t, &createHookRequest{}, "{}")

	v := &createHookRequest{
		Name:   "abc",
		Events: []string{"1", "2", "3"},
		Active: Bool(true),
		Config: map[string]interface{}{
			"thing": "@123",
		},
	}

	want := `{
		"name": "abc",
		"active": true,
		"events": ["1","2","3"],
		"config":{
			"thing": "@123"
		}
	}`

	testJSONMarshal(t, v, want)
}

func TestBranchHook_Marshal(t *testing.T) {
	testJSONMarshal(t, &Hook{}, "{}")

	v := &Hook{
		CreatedAt: &referenceTime,
		UpdatedAt: &referenceTime,
		URL:       String("url"),
		ID:        Int64(1),
		Type:      String("type"),
		Name:      String("name"),
		TestURL:   String("testurl"),
		PingURL:   String("pingurl"),
		LastResponse: map[string]interface{}{
			"item": "item",
		},
		Config: map[string]interface{}{
			"thing": "@123",
		},
		Events: []string{"1", "2", "3"},
		Active: Bool(true),
	}

	want := `{
		"created_at": ` + referenceTimeStr + `,
		"updated_at": ` + referenceTimeStr + `,
		"url": "url",
		"id": 1,
		"type": "type",
		"name": "name",
		"test_url": "testurl",
		"ping_url": "pingurl",
		"last_response":{
			"item": "item"
		},
		"config":{
			"thing": "@123"
		},
		"events": ["1","2","3"],
		"active": true
	}`

	testJSONMarshal(t, v, want)
}
<|endoftext|>


<commit_before>package main_test

import (
	"crypto/tls"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"io/ioutil"
	"net/http"
	"net/url"
	"os/exec"
	"strings"
)

var _ = Describe("Assuming chaos-galago is deployed", func() {
	var client *http.Client

	BeforeEach(func() {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		client = &http.Client{Transport: tr}
	})

	Describe("service instances", func() {
		Context("when the service instance exists", func() {
			var (
				serviceInstanceName = "galago_smoke_test"
				dashboardURL        string
			)

			BeforeEach(func() {
				exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Run()
				output, err := exec.Command("cf", "service", serviceInstanceName).Output()
				freakOutDebug(output, err)
				firstSplit := strings.SplitAfter(string(output), "Dashboard: ")[1]
				dashboardURL = strings.TrimSpace(strings.SplitAfter(firstSplit, "\n")[0])
			})

			AfterEach(func() {
				exec.Command("cf", "delete-service", "-f", serviceInstanceName).Run()
			})

			It("creates a service instance", func() {
				output, _ := exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("already exists"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).To(MatchRegexp(serviceInstanceName))
			})

			It("updates a service instance", func() {
				resp, _ := client.PostForm(dashboardURL, url.Values{"probability": {"1"}, "frequency": {"1"}})
				defer resp.Body.Close()
				body, _ := ioutil.ReadAll(resp.Body)
				Expect(string(body)).To(MatchRegexp("Probability: 1"))
				Expect(string(body)).To(MatchRegexp("Frequency: 1"))
			})

			It("deletes a service instance", func() {
				output, _ := exec.Command("cf", "delete-service", "-f", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("does not exist"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).ToNot(MatchRegexp(serviceInstanceName))
			})
		})

		Context("When the service instance does not exist", func() {
			var serviceInstanceName = "galago_smoke_test"

			AfterEach(func() {
				exec.Command("cf", "delete-service", "-f", serviceInstanceName).Run()
			})

			It("creates a service instance", func() {
				output, _ := exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("already exists"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).To(MatchRegexp(serviceInstanceName))
			})

			It("deletes a service instance", func() {
				output, _ := exec.Command("cf", "delete-service", "-f", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("does not exist"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).ToNot(MatchRegexp(serviceInstanceName))
			})
		})

	})

	Describe("service bindings", func() {
		var (
			serviceInstanceName = "galago_smoke_test"
			appName             = "galago_smoke_test"
		)

		BeforeEach(func() {
			exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Run()
			exec.Command("cf", "push", "-f", "fixtures/galago_smoke_test/manifest.yml", "--no-start").Run()
		})

		AfterEach(func() {
			exec.Command("cf", "delete", "-f", appName).Run()
			exec.Command("cf", "delete-service", "-f", serviceInstanceName).Run()
		})

		Context("when an app is bound", func() {
			BeforeEach(func() {
				exec.Command("cf", "bind-service", appName, serviceInstanceName).Run()
			})

			It("bind a service instance", func() {
				output, _ := exec.Command("cf", "bind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("already bound"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).To(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).To(MatchRegexp(`frequency": 5`))
				Expect(string(output)).To(MatchRegexp(`probability": 0.2`))
			})

			It("unbind a service instance", func() {
				output, _ := exec.Command("cf", "unbind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("did not exist"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).ToNot(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).ToNot(MatchRegexp(`frequency": 5`))
				Expect(string(output)).ToNot(MatchRegexp(`probability": 0.2`))
			})

			Context("the processor", func() {
				var (
					appGUID      string
					dashboardURL string
				)

				BeforeEach(func() {
					output, err := exec.Command("cf", "app", appName, "--guid").Output()
					freakOutDebug(output, err)
					appGUID = strings.TrimSpace(string(output))
					output, err = exec.Command("cf", "service", serviceInstanceName).Output()
					freakOutDebug(output, err)
					firstSplit := strings.SplitAfter(string(output), "Dashboard: ")[1]
					dashboardURL = strings.TrimSpace(strings.SplitAfter(firstSplit, "\n")[0])
					client.PostForm(dashboardURL, url.Values{"probability": {"1"}, "frequency": {"1"}})
					exec.Command("cf", "start", appName).Run()
					exec.Command("cf", "target", "-o", "chaos-galago", "-s", "chaos-galago").Run()
				})

				AfterEach(func() {
					exec.Command("cf", "target", "-o", orgName, "-s", spaceName).Run()
				})

				It("acts on bound aplications", func() {
					Eventually(func() string {
						output, _ := exec.Command("cf", "curl", fmt.Sprintf("v2/apps/%s/instances", appGUID)).Output()
						return string(output)
					}, "50s", "5s").Should(MatchRegexp(`"state": "RUNNING"`))
					Eventually(func() string {
						output, _ := exec.Command("cf", "logs", "chaos-galago-processor", "--recent").Output()
						return string(output)
					}, "120s", "10s").Should(MatchRegexp(fmt.Sprintf("About to kill app instance: %s at index: 0", appGUID)))
					Eventually(func() string {
						output, _ := exec.Command("cf", "curl", fmt.Sprintf("v2/apps/%s/instances", appGUID)).Output()
						return string(output)
					}, "30s", "3s").Should(MatchRegexp(`"state": "DOWN"`))
				})
			})
		})

		Context("when an app is not bound", func() {
			It("bind a service instance", func() {
				output, _ := exec.Command("cf", "bind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("already bound"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).To(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).To(MatchRegexp(`frequency": 5`))
				Expect(string(output)).To(MatchRegexp(`probability": 0.2`))
			})

			It("unbind a service instance", func() {
				output, _ := exec.Command("cf", "unbind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("did not exist"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).ToNot(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).ToNot(MatchRegexp(`frequency": 5`))
				Expect(string(output)).ToNot(MatchRegexp(`probability": 0.2`))
			})
		})
	})
})
<commit_msg>adjust eventually poll interval<commit_after>package main_test

import (
	"crypto/tls"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"io/ioutil"
	"net/http"
	"net/url"
	"os/exec"
	"strings"
)

var _ = Describe("Assuming chaos-galago is deployed", func() {
	var client *http.Client

	BeforeEach(func() {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		client = &http.Client{Transport: tr}
	})

	Describe("service instances", func() {
		Context("when the service instance exists", func() {
			var (
				serviceInstanceName = "galago_smoke_test"
				dashboardURL        string
			)

			BeforeEach(func() {
				exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Run()
				output, err := exec.Command("cf", "service", serviceInstanceName).Output()
				freakOutDebug(output, err)
				firstSplit := strings.SplitAfter(string(output), "Dashboard: ")[1]
				dashboardURL = strings.TrimSpace(strings.SplitAfter(firstSplit, "\n")[0])
			})

			AfterEach(func() {
				exec.Command("cf", "delete-service", "-f", serviceInstanceName).Run()
			})

			It("creates a service instance", func() {
				output, _ := exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("already exists"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).To(MatchRegexp(serviceInstanceName))
			})

			It("updates a service instance", func() {
				resp, _ := client.PostForm(dashboardURL, url.Values{"probability": {"1"}, "frequency": {"1"}})
				defer resp.Body.Close()
				body, _ := ioutil.ReadAll(resp.Body)
				Expect(string(body)).To(MatchRegexp("Probability: 1"))
				Expect(string(body)).To(MatchRegexp("Frequency: 1"))
			})

			It("deletes a service instance", func() {
				output, _ := exec.Command("cf", "delete-service", "-f", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("does not exist"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).ToNot(MatchRegexp(serviceInstanceName))
			})
		})

		Context("When the service instance does not exist", func() {
			var serviceInstanceName = "galago_smoke_test"

			AfterEach(func() {
				exec.Command("cf", "delete-service", "-f", serviceInstanceName).Run()
			})

			It("creates a service instance", func() {
				output, _ := exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("already exists"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).To(MatchRegexp(serviceInstanceName))
			})

			It("deletes a service instance", func() {
				output, _ := exec.Command("cf", "delete-service", "-f", serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("does not exist"))
				output, _ = exec.Command("cf", "services").Output()
				Expect(string(output)).ToNot(MatchRegexp(serviceInstanceName))
			})
		})

	})

	Describe("service bindings", func() {
		var (
			serviceInstanceName = "galago_smoke_test"
			appName             = "galago_smoke_test"
		)

		BeforeEach(func() {
			exec.Command("cf", "create-service", "chaos-galago", "default", serviceInstanceName).Run()
			exec.Command("cf", "push", "-f", "fixtures/galago_smoke_test/manifest.yml", "--no-start").Run()
		})

		AfterEach(func() {
			exec.Command("cf", "delete", "-f", appName).Run()
			exec.Command("cf", "delete-service", "-f", serviceInstanceName).Run()
		})

		Context("when an app is bound", func() {
			BeforeEach(func() {
				exec.Command("cf", "bind-service", appName, serviceInstanceName).Run()
			})

			It("bind a service instance", func() {
				output, _ := exec.Command("cf", "bind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("already bound"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).To(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).To(MatchRegexp(`frequency": 5`))
				Expect(string(output)).To(MatchRegexp(`probability": 0.2`))
			})

			It("unbind a service instance", func() {
				output, _ := exec.Command("cf", "unbind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("did not exist"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).ToNot(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).ToNot(MatchRegexp(`frequency": 5`))
				Expect(string(output)).ToNot(MatchRegexp(`probability": 0.2`))
			})

			Context("the processor", func() {
				var (
					appGUID      string
					dashboardURL string
				)

				BeforeEach(func() {
					output, err := exec.Command("cf", "app", appName, "--guid").Output()
					freakOutDebug(output, err)
					appGUID = strings.TrimSpace(string(output))
					output, err = exec.Command("cf", "service", serviceInstanceName).Output()
					freakOutDebug(output, err)
					firstSplit := strings.SplitAfter(string(output), "Dashboard: ")[1]
					dashboardURL = strings.TrimSpace(strings.SplitAfter(firstSplit, "\n")[0])
					client.PostForm(dashboardURL, url.Values{"probability": {"1"}, "frequency": {"1"}})
					exec.Command("cf", "start", appName).Run()
					exec.Command("cf", "target", "-o", "chaos-galago", "-s", "chaos-galago").Run()
				})

				AfterEach(func() {
					exec.Command("cf", "target", "-o", orgName, "-s", spaceName).Run()
				})

				It("acts on bound aplications", func() {
					Eventually(func() string {
						output, _ := exec.Command("cf", "curl", fmt.Sprintf("v2/apps/%s/instances", appGUID)).Output()
						return string(output)
					}, "50s", "1s").Should(MatchRegexp(`"state": "RUNNING"`))
					Eventually(func() string {
						output, _ := exec.Command("cf", "logs", "chaos-galago-processor", "--recent").Output()
						return string(output)
					}, "120s", "1s").Should(MatchRegexp(fmt.Sprintf("About to kill app instance: %s at index: 0", appGUID)))
					Eventually(func() string {
						output, _ := exec.Command("cf", "curl", fmt.Sprintf("v2/apps/%s/instances", appGUID)).Output()
						return string(output)
					}, "30s", "1s").Should(MatchRegexp(`"state": "DOWN"`))
				})
			})
		})

		Context("when an app is not bound", func() {
			It("bind a service instance", func() {
				output, _ := exec.Command("cf", "bind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).ToNot(MatchRegexp("already bound"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).To(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).To(MatchRegexp(`frequency": 5`))
				Expect(string(output)).To(MatchRegexp(`probability": 0.2`))
			})

			It("unbind a service instance", func() {
				output, _ := exec.Command("cf", "unbind-service", appName, serviceInstanceName).Output()
				Expect(string(output)).To(MatchRegexp("OK"))
				Expect(string(output)).To(MatchRegexp("did not exist"))
				output, _ = exec.Command("cf", "env", appName).Output()
				Expect(string(output)).ToNot(MatchRegexp(`label": "chaos-galago"`))
				Expect(string(output)).ToNot(MatchRegexp(`frequency": 5`))
				Expect(string(output)).ToNot(MatchRegexp(`probability": 0.2`))
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	appsv1beta1 "k8s.io/api/apps/v1beta1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

var (
	clientSet *kubernetes.Clientset
)

func initializeClients() {
	config, err := rest.InClusterConfig()
	if err != nil {
		panic(err.Error())
	}

	clientSet, err = kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	fmt.Println("Initialized clients!")
}

// GetPods returns the pods from the cluster
func GetPods() string {

	pods, err := clientSet.CoreV1().Pods("").List(metav1.ListOptions{})
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))
	return fmt.Sprintf("There are %d pods in the cluster\n", len(pods.Items))
}

// GetDeployments returns the deployments from the cluster
func GetDeployments() string {

	return "Trying to get Kubernetes deployments!"
}

// GetNamespaces returns all namespaces in the cluster
func GetNamespaces() (string, error) {
	namespaces, err := clientSet.CoreV1().Namespaces().List(metav1.ListOptions{})
	if err != nil {
		return "Cannot get namespaces", err
	}

	var responseString = "Namespaces in cluster: "

	for _, namespace := range namespaces.Items {
		responseString += namespace.Name + ", "
	}

	return responseString, nil
}

// GetClusterInformation return generic information about the cluster
func GetClusterInformation() string {

	var responseString = "Welcome to Azure Summer School, here is some information about your cluster: "

	namespaces, err := clientSet.CoreV1().Namespaces().List(metav1.ListOptions{})
	if err != nil {
		return "Cannot get namespaces"
	}
	responseString += fmt.Sprintf("You have %d namespaces ", len(namespaces.Items))

	pods, err := getKubernetesPods()
	if err != nil {
		return "Cannot get pods"
	}
	responseString += fmt.Sprintf("with %d containers, ", len(pods.Items))

	services, err := getKubernetesServices()
	if err != nil {
		return "Cannot get services"
	}
	responseString += fmt.Sprintf("There are %d public services", len(services.Items)) + " and all systems are up and running!"

	return responseString
}

func getKubernetesPods() (*v1.PodList, error) {
	pods, err := clientSet.CoreV1().Pods("").List(metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	return pods, nil
}

func getKubernetesServices() (*v1.ServiceList, error) {
	services, err := clientSet.CoreV1().Services("").List(metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	return services, nil
}

func CreateDeployment() string {

	deploymentsClient := clientSet.AppsV1beta1().Deployments(v1.NamespaceDefault)

	deployment := &appsv1beta1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name: "azure-summerschool",
		},
		Spec: appsv1beta1.DeploymentSpec{
			Replicas: int32Ptr(2),
			Template: v1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": "demo",
					},
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:  "azure-summerschool",
							Image: "radumatei/azure-summerschool",
							Ports: []v1.ContainerPort{
								{
									Name:          "http",
									Protocol:      v1.ProtocolTCP,
									ContainerPort: 80,
								},
							},
						},
					},
				},
			},
		},
	}

	result, err := deploymentsClient.Create(deployment)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())

	return "Created deployment! Check the public IP!"
}

func int32Ptr(i int32) *int32 { return &i }
<commit_msg>Update greeting message<commit_after>package main

import (
	"fmt"

	appsv1beta1 "k8s.io/api/apps/v1beta1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

var (
	clientSet *kubernetes.Clientset
)

func initializeClients() {
	config, err := rest.InClusterConfig()
	if err != nil {
		panic(err.Error())
	}

	clientSet, err = kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	fmt.Println("Initialized clients!")
}

// GetPods returns the pods from the cluster
func GetPods() string {

	pods, err := clientSet.CoreV1().Pods("").List(metav1.ListOptions{})
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))
	return fmt.Sprintf("There are %d pods in the cluster\n", len(pods.Items))
}

// GetDeployments returns the deployments from the cluster
func GetDeployments() string {

	return "Trying to get Kubernetes deployments!"
}

// GetNamespaces returns all namespaces in the cluster
func GetNamespaces() (string, error) {
	namespaces, err := clientSet.CoreV1().Namespaces().List(metav1.ListOptions{})
	if err != nil {
		return "Cannot get namespaces", err
	}

	var responseString = "Namespaces in cluster: "

	for _, namespace := range namespaces.Items {
		responseString += namespace.Name + ", "
	}

	return responseString, nil
}

// GetClusterInformation return generic information about the cluster
func GetClusterInformation() string {

	var responseString = "Hi, there, here is some information about your cluster: "

	namespaces, err := clientSet.CoreV1().Namespaces().List(metav1.ListOptions{})
	if err != nil {
		return "Cannot get namespaces"
	}
	responseString += fmt.Sprintf("You have %d namespaces ", len(namespaces.Items))

	pods, err := getKubernetesPods()
	if err != nil {
		return "Cannot get pods"
	}
	responseString += fmt.Sprintf("with %d containers, ", len(pods.Items))

	services, err := getKubernetesServices()
	if err != nil {
		return "Cannot get services"
	}
	responseString += fmt.Sprintf("There are %d public services", len(services.Items)) + " and all systems are up and running!"

	return responseString
}

func getKubernetesPods() (*v1.PodList, error) {
	pods, err := clientSet.CoreV1().Pods("").List(metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	return pods, nil
}

func getKubernetesServices() (*v1.ServiceList, error) {
	services, err := clientSet.CoreV1().Services("").List(metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	return services, nil
}

func CreateDeployment() string {

	deploymentsClient := clientSet.AppsV1beta1().Deployments(v1.NamespaceDefault)

	deployment := &appsv1beta1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name: "azure-summerschool",
		},
		Spec: appsv1beta1.DeploymentSpec{
			Replicas: int32Ptr(2),
			Template: v1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": "demo",
					},
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:  "azure-summerschool",
							Image: "radumatei/azure-summerschool",
							Ports: []v1.ContainerPort{
								{
									Name:          "http",
									Protocol:      v1.ProtocolTCP,
									ContainerPort: 80,
								},
							},
						},
					},
				},
			},
		},
	}

	result, err := deploymentsClient.Create(deployment)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())

	return "Created deployment! Check the public IP!"
}

func int32Ptr(i int32) *int32 { return &i }
<|endoftext|>


<commit_before>package commands

import (
	"context"
	"errors"
	"sort"
	"strings"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/clockwork"
)

var ErrInvalidCommand = errors.New("invalid command")
var ErrInvalidArguments = errors.New("invalid arguments")

type Source struct {
	globals.Contextified
	utils.DebugLabeler

	allCmds  map[int]types.ConversationCommand
	builtins map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand
	botCmd   *Bot
	clock    clockwork.Clock
}

func NewSource(g *globals.Context) *Source {
	s := &Source{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "Commands.Source", false),
		clock:        clockwork.NewRealClock(),
		botCmd:       NewBot(g),
	}
	s.makeBuiltins()
	return s
}

const (
	cmdCollapse int = iota
	cmdExpand
	cmdFlip
	cmdGiphy
	cmdHeadline
	cmdHide
	cmdJoin
	cmdLeave
	cmdLocation
	cmdMe
	cmdMsg
	cmdMute
	cmdShrug
	cmdUnhide
)

func (s *Source) allCommands() (res map[int]types.ConversationCommand) {
	res = make(map[int]types.ConversationCommand)
	res[cmdCollapse] = NewCollapse(s.G())
	res[cmdExpand] = NewExpand(s.G())
	res[cmdFlip] = NewFlip(s.G())
	res[cmdGiphy] = NewGiphy(s.G())
	res[cmdHeadline] = NewHeadline(s.G())
	res[cmdHide] = NewHide(s.G())
	res[cmdJoin] = NewJoin(s.G())
	res[cmdLeave] = NewLeave(s.G())
	res[cmdLocation] = NewLocation(s.G())
	res[cmdMe] = NewMe(s.G())
	res[cmdMsg] = NewMsg(s.G())
	res[cmdMute] = NewMute(s.G())
	res[cmdShrug] = NewShrug(s.G())
	res[cmdUnhide] = NewUnhide(s.G())
	return res
}

func (s *Source) makeBuiltins() {
	s.allCmds = s.allCommands()
	cmds := s.allCmds
	common := []types.ConversationCommand{
		cmds[cmdCollapse],
		cmds[cmdExpand],
		cmds[cmdFlip],
		cmds[cmdGiphy],
		cmds[cmdHeadline],
		cmds[cmdHide],
		cmds[cmdMe],
		cmds[cmdMsg],
		cmds[cmdMute],
		cmds[cmdShrug],
		cmds[cmdUnhide],
	}
	if s.G().IsMobileAppType() || s.G().GetRunMode() == libkb.DevelRunMode {
		common = append(common, cmds[cmdLocation])
	}
	s.builtins = make(map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand)
	s.builtins[chat1.ConversationBuiltinCommandTyp_ADHOC] = common
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAM] = append([]types.ConversationCommand{
		cmds[cmdJoin],
		cmds[cmdLeave],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL] = append([]types.ConversationCommand{
		cmds[cmdJoin],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_SMALLTEAM] = append([]types.ConversationCommand{
		cmds[cmdJoin],
	}, common...)
	for _, cmds := range s.builtins {
		sort.Slice(cmds, func(i, j int) bool {
			return cmds[i].Name() < cmds[j].Name()
		})
	}
}

func (s *Source) SetClock(clock clockwork.Clock) {
	s.clock = clock
	s.allCmds[cmdLocation].(*Location).SetClock(clock)
}

func (s *Source) GetBuiltins(ctx context.Context) (res []chat1.BuiltinCommandGroup) {
	for typ, cmds := range s.builtins {
		var exportCmds []chat1.ConversationCommand
		for _, cmd := range cmds {
			exportCmds = append(exportCmds, cmd.Export())
		}
		res = append(res, chat1.BuiltinCommandGroup{
			Typ:      typ,
			Commands: exportCmds,
		})
	}
	sort.Slice(res, func(i, j int) bool {
		return res[i].Typ < res[j].Typ
	})
	return res
}

func (s *Source) GetBuiltinCommandType(ctx context.Context, c types.ConversationCommandsSpec) chat1.ConversationBuiltinCommandTyp {
	switch c.GetMembersType() {
	case chat1.ConversationMembersType_TEAM:
		switch c.GetTeamType() {
		case chat1.TeamType_COMPLEX:
			if c.GetTopicName() == globals.DefaultTeamTopic {
				return chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL
			}
			return chat1.ConversationBuiltinCommandTyp_BIGTEAM
		default:
			return chat1.ConversationBuiltinCommandTyp_SMALLTEAM
		}
	default:
		return chat1.ConversationBuiltinCommandTyp_ADHOC
	}
}

func (s *Source) ListCommands(ctx context.Context, uid gregor1.UID, conv types.ConversationCommandsSpec) (res chat1.ConversationCommandGroups, err error) {
	defer s.Trace(ctx, func() error { return err }, "ListCommands")()
	return chat1.NewConversationCommandGroupsWithBuiltin(s.GetBuiltinCommandType(ctx, conv)), nil
}

func (s *Source) AttemptBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName string, body chat1.MessageBody, replyTo *chat1.MessageID) (handled bool, err error) {
	defer s.Trace(ctx, func() error { return err }, "AttemptBuiltinCommand")()
	if !body.IsType(chat1.MessageType_TEXT) {
		return false, nil
	}
	text := body.Text().Body
	if !strings.HasPrefix(text, "/") {
		return false, nil
	}
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return false, err
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		if cmd.Match(ctx, text) {
			s.Debug(ctx, "AttemptBuiltinCommand: matched command: %s, executing...", cmd.Name())
			return true, cmd.Execute(ctx, uid, convID, tlfName, text, replyTo)
		}
	}
	return false, nil
}

func (s *Source) PreviewBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName, text string) {
	defer s.Trace(ctx, func() error { return nil }, "PreviewBuiltinCommand")()

	// always try bot command, it might do something and is mutually exclusive with the rest of this
	// function
	s.botCmd.Preview(ctx, uid, convID, tlfName, text)

	// we let all strings through at this point, since we might need to clear a preview in a command
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		// Run preview on everything as long as it is a slash command
		cmd.Preview(ctx, uid, convID, tlfName, text)
	}
}
<commit_msg>chat: restore admin code<commit_after>package commands

import (
	"context"
	"errors"
	"sort"
	"strings"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/clockwork"
)

var ErrInvalidCommand = errors.New("invalid command")
var ErrInvalidArguments = errors.New("invalid arguments")

type Source struct {
	globals.Contextified
	utils.DebugLabeler

	allCmds  map[int]types.ConversationCommand
	builtins map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand
	botCmd   *Bot
	clock    clockwork.Clock
}

func NewSource(g *globals.Context) *Source {
	s := &Source{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "Commands.Source", false),
		clock:        clockwork.NewRealClock(),
		botCmd:       NewBot(g),
	}
	s.makeBuiltins()
	return s
}

const (
	cmdCollapse int = iota
	cmdExpand
	cmdFlip
	cmdGiphy
	cmdHeadline
	cmdHide
	cmdJoin
	cmdLeave
	cmdLocation
	cmdMe
	cmdMsg
	cmdMute
	cmdShrug
	cmdUnhide
)

func (s *Source) allCommands() (res map[int]types.ConversationCommand) {
	res = make(map[int]types.ConversationCommand)
	res[cmdCollapse] = NewCollapse(s.G())
	res[cmdExpand] = NewExpand(s.G())
	res[cmdFlip] = NewFlip(s.G())
	res[cmdGiphy] = NewGiphy(s.G())
	res[cmdHeadline] = NewHeadline(s.G())
	res[cmdHide] = NewHide(s.G())
	res[cmdJoin] = NewJoin(s.G())
	res[cmdLeave] = NewLeave(s.G())
	res[cmdLocation] = NewLocation(s.G())
	res[cmdMe] = NewMe(s.G())
	res[cmdMsg] = NewMsg(s.G())
	res[cmdMute] = NewMute(s.G())
	res[cmdShrug] = NewShrug(s.G())
	res[cmdUnhide] = NewUnhide(s.G())
	return res
}

func (s *Source) makeBuiltins() {
	s.allCmds = s.allCommands()
	cmds := s.allCmds
	common := []types.ConversationCommand{
		cmds[cmdCollapse],
		cmds[cmdExpand],
		cmds[cmdFlip],
		cmds[cmdGiphy],
		cmds[cmdHeadline],
		cmds[cmdHide],
		cmds[cmdMe],
		cmds[cmdMsg],
		cmds[cmdMute],
		cmds[cmdShrug],
		cmds[cmdUnhide],
	}
	if s.G().IsMobileAppType() || s.G().GetRunMode() == libkb.DevelRunMode {
		common = append(common, cmds[cmdLocation])
	}
	s.builtins = make(map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand)
	s.builtins[chat1.ConversationBuiltinCommandTyp_ADHOC] = common
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAM] = append([]types.ConversationCommand{
		cmds[cmdJoin],
		cmds[cmdLeave],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL] = append([]types.ConversationCommand{
		cmds[cmdJoin],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_SMALLTEAM] = append([]types.ConversationCommand{
		cmds[cmdJoin],
	}, common...)
	for _, cmds := range s.builtins {
		sort.Slice(cmds, func(i, j int) bool {
			return cmds[i].Name() < cmds[j].Name()
		})
	}
}

func (s *Source) SetClock(clock clockwork.Clock) {
	s.clock = clock
	s.allCmds[cmdLocation].(*Location).SetClock(clock)
}

func (s *Source) GetBuiltins(ctx context.Context) (res []chat1.BuiltinCommandGroup) {
	for typ, cmds := range s.builtins {
		var exportCmds []chat1.ConversationCommand
		for _, cmd := range cmds {
			exportCmds = append(exportCmds, cmd.Export())
		}
		res = append(res, chat1.BuiltinCommandGroup{
			Typ:      typ,
			Commands: exportCmds,
		})
	}
	sort.Slice(res, func(i, j int) bool {
		return res[i].Typ < res[j].Typ
	})
	return res
}

func (s *Source) GetBuiltinCommandType(ctx context.Context, c types.ConversationCommandsSpec) chat1.ConversationBuiltinCommandTyp {
	switch c.GetMembersType() {
	case chat1.ConversationMembersType_TEAM:
		switch c.GetTeamType() {
		case chat1.TeamType_COMPLEX:
			if c.GetTopicName() == globals.DefaultTeamTopic {
				return chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL
			}
			return chat1.ConversationBuiltinCommandTyp_BIGTEAM
		default:
			return chat1.ConversationBuiltinCommandTyp_SMALLTEAM
		}
	default:
		return chat1.ConversationBuiltinCommandTyp_ADHOC
	}
}

func (s *Source) ListCommands(ctx context.Context, uid gregor1.UID, conv types.ConversationCommandsSpec) (res chat1.ConversationCommandGroups, err error) {
	defer s.Trace(ctx, func() error { return err }, "ListCommands")()
	return chat1.NewConversationCommandGroupsWithBuiltin(s.GetBuiltinCommandType(ctx, conv)), nil
}

func (s *Source) AttemptBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName string, body chat1.MessageBody, replyTo *chat1.MessageID) (handled bool, err error) {
	defer s.Trace(ctx, func() error { return err }, "AttemptBuiltinCommand")()
	if !body.IsType(chat1.MessageType_TEXT) {
		return false, nil
	}
	text := body.Text().Body
	if !strings.HasPrefix(text, "/") {
		return false, nil
	}
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return false, err
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		if cmd.Match(ctx, text) {
			s.Debug(ctx, "AttemptBuiltinCommand: matched command: %s, executing...", cmd.Name())
			return true, cmd.Execute(ctx, uid, convID, tlfName, text, replyTo)
		}
	}
	return false, nil
}

func (s *Source) PreviewBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName, text string) {
	defer s.Trace(ctx, func() error { return nil }, "PreviewBuiltinCommand")()

	// always try bot command, it might do something and is mutually exclusive with the rest of this
	// function
	s.botCmd.Preview(ctx, uid, convID, tlfName, text)

	// we let all strings through at this point, since we might need to clear a preview in a command
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		// Run preview on everything as long as it is a slash command
		cmd.Preview(ctx, uid, convID, tlfName, text)
	}
}

func (s *Source) isAdmin() bool { //nolint
	username := s.G().GetEnv().GetUsername().String()
	return admins[username]
}

var admins = map[string]bool{ //nolint
	"mikem":         true,
	"max":           true,
	"candrencil64":  true,
	"chris":         true,
	"chrisnojima":   true,
	"mlsteele":      true,
	"xgess":         true,
	"karenm":        true,
	"kb_monbot":     true,
	"joshblum":      true,
	"cjb":           true,
	"jzila":         true,
	"patrick":       true,
	"modalduality":  true,
	"strib":         true,
	"songgao":       true,
	"ayoubd":        true,
	"cecileb":       true,
	"adamjspooner":  true,
	"akalin":        true,
	"marcopolo":     true,
	"aimeedavid":    true,
	"jinyang":       true,
	"zapu":          true,
	"jakob223":      true,
	"taruti":        true,
	"pzduniak":      true,
	"zanderz":       true,
	"giphy_tester":  true,
	"candrencil983": true,
	"candrencil889": true,
	"candrencil911": true,
}
<|endoftext|>


<commit_before>package webservice

import (
	ae "appengine"
	ds "appengine/datastore"
	"cache"
	"data"
	"encoding/json"
	"errors"
	"fmt"
	"googlebooks"
	"isbn13"
	"log"
	"net/http"
	"persistence"
)

func init() {
	http.HandleFunc("/volumes/", serveLookup)
}

func LookupISBN(ctx ae.Context, country string, isbn isbn13.ISBN13) (resp *data.BookMetaData, err error) {
	funcs := []func(ae.Context, string, isbn13.ISBN13) (*data.BookMetaData, error){
		func(ctx ae.Context, country string, isbn isbn13.ISBN13) (*data.BookMetaData, error) {
			shelf, err := persistence.LookupBookshelf(ctx)
			if err == nil {
				return shelf.LookupInfo(isbn)
			}

			return nil, err
		},
		cache.LookupISBN,
		persistence.LookupISBN,
		func(ctx ae.Context, country string, isbn isbn13.ISBN13) (*data.BookMetaData, error) {
			r, err := googlebooks.LookupISBN(ctx, country, isbn)

			if err == nil {
				go cache.CacheISBNResult(ctx, country, isbn, r)
				go persistence.StoreISBNResult(ctx, country, isbn, r)
			}

			return r, err
		},
	}

	var multi ae.MultiError

	for i, f := range funcs {
		if result, err := f(ctx, country, isbn); err == nil && result != nil {
			log.Printf("Found info %v after %d iterations\n", result, i)
			return result, nil
		} else {
			multi = append(multi, err)
		}
	}

	return nil, multi
}

func serveLookup(w http.ResponseWriter, rq *http.Request) {
	isbn, err := isbn13.New(rq.URL.Path[9:])
	status := http.StatusBadRequest

	if err == nil {
		status = http.StatusInternalServerError
		switch rq.Method {
		case "GET":
			err = handleGet(w, rq, isbn)
		case "PUT":
			err = handlePut(w, rq, isbn)
		default:
			status = http.StatusMethodNotAllowed
			err = errors.New("Unsupported operation. Only GET, PUT and DELETE methods are allowed")
		}

	}

	if err != nil {
		w.WriteHeader(status)
		fmt.Fprintf(w, "Ooops, %s (%v)!\n", rq.URL.Path, err)
	}
}

func handlePut(w http.ResponseWriter, rq *http.Request, isbn isbn13.ISBN13) error {
	ctx := ae.NewContext(rq)
	shelf, err := persistence.LookupBookshelf(ctx)

	if err == ds.ErrNoSuchEntity {
		err = nil
		shelf = new(data.Bookshelf)
	}

	if err == nil {
		decode := json.NewDecoder(rq.Body)
		info := new(data.BookMetaData)
		if err = decode.Decode(info); err == nil {

			shelf.Books = append(shelf.Books, *info)

		}
	}
	return err

}

func handleGet(w http.ResponseWriter, rq *http.Request, isbn isbn13.ISBN13) error {
	var reply *data.BookMetaData
	var err error
	ctx := ae.NewContext(rq)
	if reply, err = LookupISBN(ctx, "de", isbn); err == nil {
		encode := json.NewEncoder(w)
		w.WriteHeader(http.StatusOK)
		w.Header().Add("Content-Type", "application/json")
		if err = encode.Encode(reply); err == nil {
			return nil
		}
	}

	return err
}
<commit_msg>Now support put-as-replace<commit_after>package webservice

import (
	ae "appengine"
	ds "appengine/datastore"
	"cache"
	"data"
	"encoding/json"
	"errors"
	"fmt"
	"googlebooks"
	"isbn13"
	"log"
	"net/http"
	"persistence"
)

func init() {
	http.HandleFunc("/volumes/", serveLookup)
}

func LookupISBN(ctx ae.Context, country string, isbn isbn13.ISBN13) (resp *data.BookMetaData, err error) {
	funcs := []func(ae.Context, string, isbn13.ISBN13) (*data.BookMetaData, error){
		func(ctx ae.Context, country string, isbn isbn13.ISBN13) (*data.BookMetaData, error) {
			shelf, err := persistence.LookupBookshelf(ctx)
			if err == nil {
				return shelf.LookupInfo(isbn)
			}

			return nil, err
		},
		cache.LookupISBN,
		persistence.LookupISBN,
		func(ctx ae.Context, country string, isbn isbn13.ISBN13) (*data.BookMetaData, error) {
			r, err := googlebooks.LookupISBN(ctx, country, isbn)

			if err == nil {
				go cache.CacheISBNResult(ctx, country, isbn, r)
				go persistence.StoreISBNResult(ctx, country, isbn, r)
			}

			return r, err
		},
	}

	var multi ae.MultiError

	for i, f := range funcs {
		if result, err := f(ctx, country, isbn); err == nil && result != nil {
			log.Printf("Found info %v after %d iterations\n", result, i)
			return result, nil
		} else {
			multi = append(multi, err)
		}
	}

	return nil, multi
}

func serveLookup(w http.ResponseWriter, rq *http.Request) {
	isbn, err := isbn13.New(rq.URL.Path[9:])
	status := http.StatusBadRequest

	if err == nil {
		status = http.StatusInternalServerError
		switch rq.Method {
		case "GET":
			err = handleGet(w, rq, isbn)
		case "PUT":
			err = handlePut(w, rq, isbn)
		default:
			status = http.StatusMethodNotAllowed
			err = errors.New("Unsupported operation. Only GET, PUT and DELETE methods are allowed")
		}

	}

	if err != nil {
		w.WriteHeader(status)
		fmt.Fprintf(w, "Ooops, %s (%v)!\n", rq.URL.Path, err)
	}
}

func handlePut(w http.ResponseWriter, rq *http.Request, isbn isbn13.ISBN13) error {
	ctx := ae.NewContext(rq)
	shelf, err := persistence.LookupBookshelf(ctx)

	if err == ds.ErrNoSuchEntity {
		err = nil
		shelf = new(data.Bookshelf)
	}

	if err == nil {
		decode := json.NewDecoder(rq.Body)
		info := new(data.BookMetaData)
		if err = decode.Decode(info); err == nil {
			if ptr, _ := shelf.LookupInfo(isbn); ptr != nil {
				*ptr = *info
			} else {
				shelf.Books = append(shelf.Books, *info)
			}

		}
	}
	return err

}

func handleGet(w http.ResponseWriter, rq *http.Request, isbn isbn13.ISBN13) error {
	var reply *data.BookMetaData
	var err error
	ctx := ae.NewContext(rq)
	if reply, err = LookupISBN(ctx, "de", isbn); err == nil {
		encode := json.NewEncoder(w)
		w.WriteHeader(http.StatusOK)
		w.Header().Add("Content-Type", "application/json")
		if err = encode.Encode(reply); err == nil {
			return nil
		}
	}

	return err
}
<|endoftext|>


<commit_before>package intcode

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/bewuethr/advent-of-code/go/convert"
)

func TestComputerDay02(t *testing.T) {
	var tests = []struct {
		program string
		wantIdx int
		wantVal int
	}{
		{"1,9,10,3,2,3,11,0,99,30,40,50", 0, 3500},
		{"1,0,0,0,99", 0, 2},
		{"2,3,0,3,99", 3, 6},
		{"2,4,4,5,99,0", 5, 9801},
		{"1,1,1,4,99,5,6,0,99", 0, 30},
		{"1,1,1,4,99,5,6,0,99", 4, 2},
	}

	for _, test := range tests {
		opcodesStr := strings.Split(test.program, ",")
		opcodes, err := convert.StrSliceToInt(opcodesStr)
		if err != nil {
			t.Fatalf("unable to convert string slice: %v\n", err)
		}

		c := NewComputer(opcodes)
		c.RunProgram()
		select {
		case err := <-c.Err:
			t.Fatalf("got %v, want nil\n", err)
		case <-c.Done:
			if c.Value(test.wantIdx) != test.wantVal {
				t.Errorf("c.RunProgram(%v), v(%d) == %d, want %d\n",
					test.program, test.wantIdx, c.Value(test.wantIdx), test.wantVal)
			}
		}
	}
}

func TestComputerDay05(t *testing.T) {
	largeInput := "3,21,1008,21,8,20,1005,20,22,107,8,21,20,1006,20,31," +
		"1106,0,36,98,0,0,1002,21,125,20,4,20,1105,1,46,104," +
		"999,1105,1,46,1101,1000,1,20,4,20,1105,1,46,98,99"

	var tests = []struct {
		program string
		input   int
		want    int
	}{
		{"3,0,4,0,99", 123, 123},           // input is output
		{"3,9,8,9,10,9,4,9,99,-1,8", 8, 1}, // pos.mode, input == 8, true
		{"3,9,8,9,10,9,4,9,99,-1,8", 9, 0}, // pos.mode, input == 8, false
		{"3,9,7,9,10,9,4,9,99,-1,8", 7, 1}, // pos.mode, input < 8, true
		{"3,9,7,9,10,9,4,9,99,-1,8", 8, 0}, // pos.mode, input < 8, false
		{"3,3,1108,-1,8,3,4,3,99", 8, 1},   // imm. mode, input == 8, true
		{"3,3,1108,-1,8,3,4,3,99", 9, 0},   // imm. mode, input == 8, false
		{"3,3,1107,-1,8,3,4,3,99", 7, 1},   // imm. mode, input < 8, true
		{"3,3,1107,-1,8,3,4,3,99", 8, 0},   // imm. mode, input < 8, false

		// Jump tests: output 0 if input is zero, 1 if non-zero
		{"3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9", 0, 0}, // pos. mode, zero
		{"3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9", 1, 1}, // pos. mode, non-zero
		{"3,3,1105,-1,9,1101,0,0,12,4,12,99,1", 0, 0},      // imm. mode, zero
		{"3,3,1105,-1,9,1101,0,0,12,4,12,99,1", 1, 1},      // imm. mode, non-zero

		// Compare value to 8, output 999/1000/1001 for less/equal/greater
		{largeInput, 7, 999},  // input < 8
		{largeInput, 8, 1000}, // input == 8
		{largeInput, 9, 1001}, // input > 8
	}

	for _, test := range tests {
		opcodesStr := strings.Split(test.program, ",")
		opcodes, err := convert.StrSliceToInt(opcodesStr)
		if err != nil {
			t.Fatalf("unable to convert string slice: %v\n", err)
		}

		c := NewComputer(opcodes)
		c.RunProgram()
		c.Input <- test.input
		var got int
	Loop:
		for {
			select {
			case v := <-c.Output:
				fmt.Println(v)
				got = v
			case <-c.Done:
				break Loop
			case err := <-c.Err:
				t.Fatalf("input %v caused %v, want nil", test.program, err)
			}
		}

		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("program %v, input %v, got %v, want %v",
				test.program, test.input, got, test.want)
		}
	}
}
<commit_msg>Remove test output<commit_after>package intcode

import (
	"reflect"
	"strings"
	"testing"

	"github.com/bewuethr/advent-of-code/go/convert"
)

func TestComputerDay02(t *testing.T) {
	var tests = []struct {
		program string
		wantIdx int
		wantVal int
	}{
		{"1,9,10,3,2,3,11,0,99,30,40,50", 0, 3500},
		{"1,0,0,0,99", 0, 2},
		{"2,3,0,3,99", 3, 6},
		{"2,4,4,5,99,0", 5, 9801},
		{"1,1,1,4,99,5,6,0,99", 0, 30},
		{"1,1,1,4,99,5,6,0,99", 4, 2},
	}

	for _, test := range tests {
		opcodesStr := strings.Split(test.program, ",")
		opcodes, err := convert.StrSliceToInt(opcodesStr)
		if err != nil {
			t.Fatalf("unable to convert string slice: %v\n", err)
		}

		c := NewComputer(opcodes)
		c.RunProgram()
		select {
		case err := <-c.Err:
			t.Fatalf("got %v, want nil\n", err)
		case <-c.Done:
			if c.Value(test.wantIdx) != test.wantVal {
				t.Errorf("c.RunProgram(%v), v(%d) == %d, want %d\n",
					test.program, test.wantIdx, c.Value(test.wantIdx), test.wantVal)
			}
		}
	}
}

func TestComputerDay05(t *testing.T) {
	largeInput := "3,21,1008,21,8,20,1005,20,22,107,8,21,20,1006,20,31," +
		"1106,0,36,98,0,0,1002,21,125,20,4,20,1105,1,46,104," +
		"999,1105,1,46,1101,1000,1,20,4,20,1105,1,46,98,99"

	var tests = []struct {
		program string
		input   int
		want    int
	}{
		{"3,0,4,0,99", 123, 123},           // input is output
		{"3,9,8,9,10,9,4,9,99,-1,8", 8, 1}, // pos.mode, input == 8, true
		{"3,9,8,9,10,9,4,9,99,-1,8", 9, 0}, // pos.mode, input == 8, false
		{"3,9,7,9,10,9,4,9,99,-1,8", 7, 1}, // pos.mode, input < 8, true
		{"3,9,7,9,10,9,4,9,99,-1,8", 8, 0}, // pos.mode, input < 8, false
		{"3,3,1108,-1,8,3,4,3,99", 8, 1},   // imm. mode, input == 8, true
		{"3,3,1108,-1,8,3,4,3,99", 9, 0},   // imm. mode, input == 8, false
		{"3,3,1107,-1,8,3,4,3,99", 7, 1},   // imm. mode, input < 8, true
		{"3,3,1107,-1,8,3,4,3,99", 8, 0},   // imm. mode, input < 8, false

		// Jump tests: output 0 if input is zero, 1 if non-zero
		{"3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9", 0, 0}, // pos. mode, zero
		{"3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9", 1, 1}, // pos. mode, non-zero
		{"3,3,1105,-1,9,1101,0,0,12,4,12,99,1", 0, 0},      // imm. mode, zero
		{"3,3,1105,-1,9,1101,0,0,12,4,12,99,1", 1, 1},      // imm. mode, non-zero

		// Compare value to 8, output 999/1000/1001 for less/equal/greater
		{largeInput, 7, 999},  // input < 8
		{largeInput, 8, 1000}, // input == 8
		{largeInput, 9, 1001}, // input > 8
	}

	for _, test := range tests {
		opcodesStr := strings.Split(test.program, ",")
		opcodes, err := convert.StrSliceToInt(opcodesStr)
		if err != nil {
			t.Fatalf("unable to convert string slice: %v\n", err)
		}

		c := NewComputer(opcodes)
		c.RunProgram()
		c.Input <- test.input
		var got int
	Loop:
		for {
			select {
			case v := <-c.Output:
				got = v
			case <-c.Done:
				break Loop
			case err := <-c.Err:
				t.Fatalf("input %v caused %v, want nil", test.program, err)
			}
		}

		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("program %v, input %v, got %v, want %v",
				test.program, test.input, got, test.want)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package systests

import (
	"bytes"
	"strings"
	"testing"
	"time"

	"github.com/keybase/client/go/client"
	"github.com/keybase/client/go/gregor"
	"github.com/keybase/client/go/libkb"
	gregor1 "github.com/keybase/client/go/protocol/gregor1"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/service"
	"github.com/keybase/go-framed-msgpack-rpc/rpc"
	"github.com/stretchr/testify/require"
	context "golang.org/x/net/context"
)

type electronMock struct {
	libkb.Contextified
	errCh   chan error
	stateCh chan keybase1.PushStateArg
	oobmCh  chan gregor1.OutOfBandMessage
}

func (e *electronMock) PushState(ctx context.Context, a keybase1.PushStateArg) (err error) {
	e.G().Log.Debug("electronMock::PushState: %#v\n", a)
	e.stateCh <- a
	return nil
}

func (e *electronMock) PushOutOfBandMessages(_ context.Context, msgs []gregor1.OutOfBandMessage) error {
	for _, m := range msgs {
		e.oobmCh <- m
	}
	return nil
}

func newElectronMock(g *libkb.GlobalContext) *electronMock {
	return &electronMock{
		Contextified: libkb.NewContextified(g),
		errCh:        make(chan error, 1),
		stateCh:      make(chan keybase1.PushStateArg, 10),
		oobmCh:       make(chan gregor1.OutOfBandMessage, 10),
	}
}

// filterPubsubdItems removes pubsubd generated IBMs from state
func filterPubsubdItems(items []gregor1.ItemAndMetadata) (res []gregor1.ItemAndMetadata) {
	for _, i := range items {
		categoryStr := i.Category().String()
		if !strings.HasPrefix(categoryStr, "user.") &&
			!strings.HasPrefix(categoryStr, "stellar.") &&
			!strings.HasPrefix(categoryStr, "home.") {
			res = append(res, i)
		}
	}
	return
}

func TestGregorForwardToElectron(t *testing.T) {
	tc := setupTest(t, "gregor")
	defer tc.Cleanup()
	tc1 := cloneContext(tc)

	svc := service.NewService(tc.G, false)
	startCh := svc.GetStartChannel()
	stopCh := make(chan error)
	go func() {
		tc.G.Log.Debug("+ Service.Run")
		err := svc.Run()
		tc.G.Log.Debug("- Service.Run")
		require.NoError(t, err)
		stopCh <- err
	}()

	userInfo := randomUser("grgr")
	sui := signupUI{
		info:         userInfo,
		Contextified: libkb.NewContextified(tc.G),
	}
	tc.G.SetUI(&sui)
	signup := client.NewCmdSignupRunner(tc.G)
	signup.SetTest()

	// Wait for the server to start up
	<-startCh
	require.NoError(t, signup.Run())

	cli, xp, err := client.GetRPCClientWithContext(tc1.G)
	srv := rpc.NewServer(xp, nil)
	em := newElectronMock(tc.G)
	err = srv.Register(keybase1.GregorUIProtocol(em))
	require.NoError(t, err)
	ncli := keybase1.DelegateUiCtlClient{Cli: cli}

	// Spin until gregor comes up; it should come up after signup
	var ok bool
	for i := 0; !ok && i < 200; i++ {
		if ok = svc.HasGregor(); !ok {
			time.Sleep(50 * time.Millisecond)
		} else {
			tc.G.Log.Debug("spinning, waiting for gregor to come up (attempt %d)", i)
		}
	}
	require.True(t, ok)

	svc.SetGregorPushStateFilter(func(m gregor.Message) bool {
		cat := m.ToInBandMessage().ToStateUpdateMessage().Creation().Category()
		return cat.String() != "user.identity_change" && cat.String() != "user.key_change" &&
			!strings.HasPrefix(cat.String(), "home.") && !strings.HasPrefix(cat.String(), "stellar.")
	})
	require.NoError(t, ncli.RegisterGregorFirehose(context.TODO()))

	select {
	case a := <-em.stateCh:
		require.Equal(t, keybase1.PushReason_RECONNECTED, a.Reason)
		require.Zero(t, filterPubsubdItems(a.State.Items_))
	case <-time.After(3 * time.Second):
		require.Fail(t, "no reconnect message")
	}

	msgID, err := svc.GregorInject("foo", []byte("bar"))
	require.NoError(t, err)
	require.NoError(t, svc.GregorInjectOutOfBandMessage("baz", []byte("bip")))

	checkState := func(s gregor1.State) {
		items := filterPubsubdItems(s.Items_)
		require.Equal(t, 1, len(items))
		i := items[0]
		require.True(t, bytes.Equal(i.Md_.MsgID_.Bytes(), msgID.Bytes()))
		require.Equal(t, "foo", i.Item_.Category_.String())
		require.Equal(t, "bar", string(i.Item_.Body_.Bytes()))
	}

	// We get two push states, one from the local send, and one from receiving broadcast
	for i := 0; i < 2; i++ {
		select {
		case pushArg := <-em.stateCh:
			checkState(pushArg.State)
			require.Equal(t, keybase1.PushReason_NEW_DATA, pushArg.Reason)
		case <-time.After(10 * time.Second):
			require.Fail(t, "no ibm")
		}
	}

	pollForTrue(t, tc.G, func(i int) bool {
		select {
		case oobm := <-em.oobmCh:
			if oobm.System_ != "baz" {
				return false
			}
			if s := string(oobm.Body_); s != "bip" {
				return false
			}
			return true
		case <-time.After(3 * time.Second * libkb.CITimeMultiplier(tc.G)):
			return false
		}
	})

	svc.SimulateGregorCrashForTesting()
	select {
	case pushArg := <-em.stateCh:
		checkState(pushArg.State)
		require.Equal(t, keybase1.PushReason_RECONNECTED, pushArg.Reason)
	case <-time.After(10 * time.Second):
		require.Fail(t, "no ibm")
	}

	gcli := keybase1.GregorClient{Cli: cli}
	state, err := gcli.GetState(context.TODO())
	require.NoError(t, err)
	checkState(state)

	require.NoError(t, client.CtlServiceStop(tc.G))
	// If the server failed, it's also an error
	require.NoError(t, <-stopCh)
}
<commit_msg>filter out device notifications in TestGregorForwardToElectron (#14272)<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package systests

import (
	"bytes"
	"strings"
	"testing"
	"time"

	"github.com/keybase/client/go/client"
	"github.com/keybase/client/go/gregor"
	"github.com/keybase/client/go/libkb"
	gregor1 "github.com/keybase/client/go/protocol/gregor1"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/service"
	"github.com/keybase/go-framed-msgpack-rpc/rpc"
	"github.com/stretchr/testify/require"
	context "golang.org/x/net/context"
)

type electronMock struct {
	libkb.Contextified
	errCh   chan error
	stateCh chan keybase1.PushStateArg
	oobmCh  chan gregor1.OutOfBandMessage
}

func (e *electronMock) PushState(ctx context.Context, a keybase1.PushStateArg) (err error) {
	e.G().Log.Debug("electronMock::PushState: %#v\n", a)
	e.stateCh <- a
	return nil
}

func (e *electronMock) PushOutOfBandMessages(_ context.Context, msgs []gregor1.OutOfBandMessage) error {
	for _, m := range msgs {
		e.oobmCh <- m
	}
	return nil
}

func newElectronMock(g *libkb.GlobalContext) *electronMock {
	return &electronMock{
		Contextified: libkb.NewContextified(g),
		errCh:        make(chan error, 1),
		stateCh:      make(chan keybase1.PushStateArg, 10),
		oobmCh:       make(chan gregor1.OutOfBandMessage, 10),
	}
}

// filterPubsubdItems removes pubsubd generated IBMs from state
func filterPubsubdItems(items []gregor1.ItemAndMetadata) (res []gregor1.ItemAndMetadata) {
	for _, i := range items {
		categoryStr := i.Category().String()
		if !strings.HasPrefix(categoryStr, "user.") &&
			!strings.HasPrefix(categoryStr, "stellar.") &&
			!strings.HasPrefix(categoryStr, "device.") &&
			!strings.HasPrefix(categoryStr, "home.") {
			res = append(res, i)
		}
	}
	return
}

func TestGregorForwardToElectron(t *testing.T) {
	tc := setupTest(t, "gregor")
	defer tc.Cleanup()
	tc1 := cloneContext(tc)

	svc := service.NewService(tc.G, false)
	startCh := svc.GetStartChannel()
	stopCh := make(chan error)
	go func() {
		tc.G.Log.Debug("+ Service.Run")
		err := svc.Run()
		tc.G.Log.Debug("- Service.Run")
		require.NoError(t, err)
		stopCh <- err
	}()

	userInfo := randomUser("grgr")
	sui := signupUI{
		info:         userInfo,
		Contextified: libkb.NewContextified(tc.G),
	}
	tc.G.SetUI(&sui)
	signup := client.NewCmdSignupRunner(tc.G)
	signup.SetTest()

	// Wait for the server to start up
	<-startCh
	require.NoError(t, signup.Run())

	cli, xp, err := client.GetRPCClientWithContext(tc1.G)
	srv := rpc.NewServer(xp, nil)
	em := newElectronMock(tc.G)
	err = srv.Register(keybase1.GregorUIProtocol(em))
	require.NoError(t, err)
	ncli := keybase1.DelegateUiCtlClient{Cli: cli}

	// Spin until gregor comes up; it should come up after signup
	var ok bool
	for i := 0; !ok && i < 200; i++ {
		if ok = svc.HasGregor(); !ok {
			time.Sleep(50 * time.Millisecond)
		} else {
			tc.G.Log.Debug("spinning, waiting for gregor to come up (attempt %d)", i)
		}
	}
	require.True(t, ok)

	svc.SetGregorPushStateFilter(func(m gregor.Message) bool {
		cat := m.ToInBandMessage().ToStateUpdateMessage().Creation().Category()
		return cat.String() != "user.identity_change" && cat.String() != "user.key_change" &&
			!strings.HasPrefix(cat.String(), "home.") && !strings.HasPrefix(cat.String(), "stellar.")
	})
	require.NoError(t, ncli.RegisterGregorFirehose(context.TODO()))

	select {
	case a := <-em.stateCh:
		require.Equal(t, keybase1.PushReason_RECONNECTED, a.Reason)
		require.Zero(t, filterPubsubdItems(a.State.Items_))
	case <-time.After(3 * time.Second):
		require.Fail(t, "no reconnect message")
	}

	msgID, err := svc.GregorInject("foo", []byte("bar"))
	require.NoError(t, err)
	require.NoError(t, svc.GregorInjectOutOfBandMessage("baz", []byte("bip")))

	checkState := func(s gregor1.State) {
		items := filterPubsubdItems(s.Items_)
		require.Equal(t, 1, len(items))
		i := items[0]
		require.True(t, bytes.Equal(i.Md_.MsgID_.Bytes(), msgID.Bytes()))
		require.Equal(t, "foo", i.Item_.Category_.String())
		require.Equal(t, "bar", string(i.Item_.Body_.Bytes()))
	}

	// We get two push states, one from the local send, and one from receiving broadcast
	for i := 0; i < 2; i++ {
		select {
		case pushArg := <-em.stateCh:
			checkState(pushArg.State)
			require.Equal(t, keybase1.PushReason_NEW_DATA, pushArg.Reason)
		case <-time.After(10 * time.Second):
			require.Fail(t, "no ibm")
		}
	}

	pollForTrue(t, tc.G, func(i int) bool {
		select {
		case oobm := <-em.oobmCh:
			if oobm.System_ != "baz" {
				return false
			}
			if s := string(oobm.Body_); s != "bip" {
				return false
			}
			return true
		case <-time.After(3 * time.Second * libkb.CITimeMultiplier(tc.G)):
			return false
		}
	})

	svc.SimulateGregorCrashForTesting()
	select {
	case pushArg := <-em.stateCh:
		checkState(pushArg.State)
		require.Equal(t, keybase1.PushReason_RECONNECTED, pushArg.Reason)
	case <-time.After(10 * time.Second):
		require.Fail(t, "no ibm")
	}

	gcli := keybase1.GregorClient{Cli: cli}
	state, err := gcli.GetState(context.TODO())
	require.NoError(t, err)
	checkState(state)

	require.NoError(t, client.CtlServiceStop(tc.G))
	// If the server failed, it's also an error
	require.NoError(t, <-stopCh)
}
<|endoftext|>