<commit_before>// Copyright 2015 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package yang

import (
	"bytes"
	"testing"
)

func (s1 *Statement) equal(s2 *Statement) bool {
	if s1.Keyword != s2.Keyword ||
		s1.HasArgument != s2.HasArgument ||
		s1.Argument != s2.Argument ||
		len(s1.statements) != len(s2.statements) {
		return false
	}

	for x, ss := range s1.statements {
		if !ss.equal(s2.statements[x]) {
			return false
		}
	}
	return true
}

// SA returns a statement with an argument and optional substatements.
func SA(k, a string, ss ...*Statement) *Statement {
	return &Statement{
		Keyword:     k,
		Argument:    a,
		HasArgument: true,
		statements:  ss,
	}
}

// S returns a statement with no argument and optional substatements.
func S(k string, ss ...*Statement) *Statement {
	return &Statement{
		Keyword:    k,
		statements: ss,
	}
}

func TestParse(t *testing.T) {
	for _, tt := range []struct {
		line int
		in   string
		out  []*Statement
		err  string
	}{
		{line: line()},
		{line: line(), in: `
foo;
`,
			out: []*Statement{
				S("foo"),
			},
		},
		{line: line(), in: `
foo {}
`,
			out: []*Statement{
				S("foo"),
			},
		},
		{line: line(), in: `
foo "";
`,
			out: []*Statement{
				SA("foo", ""),
			},
		},
		{line: line(), in: `
foo bar;
`,
			out: []*Statement{
				SA("foo", "bar"),
			},
		},
		{line: line(), in: `
foo "bar";
`,
			out: []*Statement{
				SA("foo", "bar"),
			},
		},
		{line: line(), in: `
foo "\\ \S \n";
`,
			err: `test.yang:2:5: invalid escape sequence: \S`,
		},
		{line: line(), in: `
pattern "\\ \S \n";
`,
			out: []*Statement{
				SA("pattern", `\ \S
`),
			},
		},
		{line: line(), in: `
foo '\\ \S \n';
`,
			out: []*Statement{
				SA("foo", `\\ \S \n`),
			},
		},
		{line: line(), in: `
pattern '\\ \S \n';
`,
			out: []*Statement{
				SA("pattern", `\\ \S \n`),
			},
		},
		{line: line(), in: `
foo bar;
red black;
`,
			out: []*Statement{
				SA("foo", "bar"),
				SA("red", "black"),
			},
		},
		{line: line(), in: `
foo {
   key value;
}
`,
			out: []*Statement{
				S("foo",
					SA("key", "value"),
				),
			},
		},
		{line: line(), in: `
foo {
   key value;
}
`,
			out: []*Statement{
				S("foo",
					SA("key", "value"),
				),
			},
		},
		{line: line(), in: `
foo {
   key value;
   key2;
}
`,
			out: []*Statement{
				S("foo",
					SA("key", "value"),
					S("key2"),
				),
			},
		},
		{line: line(), in: `
foo1 {
   key value1;
}
foo2 {
   key value2;
}
foo3 value3;
`,
			out: []*Statement{
				S("foo1",
					SA("key", "value1"),
				),
				S("foo2",
					SA("key", "value2"),
				),
				SA("foo3", "value3"),
			},
		},
		{line: line(), in: `
foo1 {
    key value1;
    foo2 {
        key value2;
    }
}
`,
			out: []*Statement{
				S("foo1",
					SA("key", "value1"),
					S("foo2",
						SA("key", "value2"),
					),
				),
			},
		},
		{line: line(), in: `
 }
`,
			err: `test.yang:2:2: unexpected }`,
		},
		{line: line(), in: `
id
`,
			err: `test.yang: unexpected EOF`,
		},
		{line: line(), in: `
   {
`,
			err: `test.yang:2:4: {: not an identifier`,
		},
		{line: line(), in: `
;
`,
			err: `test.yang:2:1: ;: not an identifier`,
		},
		{line: line(), in: `
statement one two { }
`,
			err: `test.yang:2:15: two: syntax error
test.yang:2:19: {: not an identifier
test.yang:2:21: unexpected }`,
		},
		{line: line(), in: `
    }
foo {
	key: "value";
}
`,
			err: `test.yang:2:5: unexpected }`,
		},
		{line: line(), in: `
{
	something: "bad";
}
foo {
	key: "\Value";
	key2: "value2";
	bar {
		key3: "value\3;
	}
}`,
			err: `test.yang:2:1: {: not an identifier
test.yang:4:1: unexpected }
test.yang:6:7: invalid escape sequence: \V
test.yang:9:9: invalid escape sequence: \3
test.yang:9:9: missing closing "
test.yang: unexpected EOF`,
		},
	} {
		s, err := Parse(tt.in, "test.yang")
		if (s == nil) != (tt.out == nil) {
			if s == nil {
				t.Errorf("%d: did not get expected statements: %s", tt.line, tt.out)
			} else {
				t.Errorf("%d: get unexpected statements: %s", tt.line, s)
			}
		}
		switch {
		case err == nil && tt.err == "":
		case tt.err == "":
			t.Errorf("%d: unexpected error %v", tt.line, err)
			continue
		case err == nil:
			t.Errorf("%d: did not get expected error %v", tt.line, tt.err)
			continue
		case err.Error() == tt.err:
			continue
		default:
			t.Errorf("%d: got error:\n%s\nwant:\n%s", tt.line, err, tt.err)
			continue
		}
		s1 := &Statement{statements: s}
		s2 := &Statement{statements: tt.out}
		if !s1.equal(s2) {
			t.Errorf("%d: got:\n%s\nwant:\n%s", tt.line, s1, s2)
		}
	}
}

func TestWrite(t *testing.T) {
Testing:
	for _, tt := range []struct {
		line int
		in   string
		out  string
	}{
		{line: line(),
			in: `key arg { substatement; }`,
			out: `key "arg" {
	substatement;
}
`,
		},
		{line: line(),
			in: `key { substatement { key arg; }}`,
			out: `key {
	substatement {
		key "arg";
	}
}
`,
		},
		{line: line(),
			in: `
module base {
   namespace "urn:mod";
   prefix "base";

   typedef base-type { type int32; }

   grouping base-group {
     description
       "The base-group is used to test the
        'uses' statement below.  This description
        is here to simply include a multi-line
        string as an example of multi-line strings";
     leaf base-group-leaf {
       config false;
       type string;
     }
   }
   uses base-group;
}
`, out: `module "base" {
	namespace "urn:mod";
	prefix "base";
	typedef "base-type" {
		type "int32";
	}
	grouping "base-group" {
		description "The base-group is used to test the
		             'uses' statement below.  This description
		             is here to simply include a multi-line
		             string as an example of multi-line strings";
		leaf "base-group-leaf" {
			config "false";
			type "string";
		}
	}
	uses "base-group";
}
`,
		},
	} {
		in := tt.in
		// Run twice.  The first time we are parsing tt.in, the second
		// time we are parsing the output from the first parsing.
		for i := 0; i < 2; i++ {
			s, err := Parse(in, "test.yang")
			if err != nil {
				t.Errorf("%d: unexpected error %v", tt.line, err)
				continue Testing
			}
			if len(s) != 1 {
				t.Errorf("%d: got %d statements, expected 1", tt.line, len(s))
				continue Testing
			}
			var buf bytes.Buffer
			s[0].Write(&buf, "")
			out := buf.String()
			if out != tt.out {
				t.Errorf("%d: got:\n%swant:\n%s", tt.line, out, tt.out)
				continue Testing
			}
			in = out
		}
	}
}
<commit_msg>Add test to show how newlines are kept when parsing the key token<commit_after>// Copyright 2015 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package yang

import (
	"bytes"
	"testing"
)

func (s1 *Statement) equal(s2 *Statement) bool {
	if s1.Keyword != s2.Keyword ||
		s1.HasArgument != s2.HasArgument ||
		s1.Argument != s2.Argument ||
		len(s1.statements) != len(s2.statements) {
		return false
	}

	for x, ss := range s1.statements {
		if !ss.equal(s2.statements[x]) {
			return false
		}
	}
	return true
}

// SA returns a statement with an argument and optional substatements.
func SA(k, a string, ss ...*Statement) *Statement {
	return &Statement{
		Keyword:     k,
		Argument:    a,
		HasArgument: true,
		statements:  ss,
	}
}

// S returns a statement with no argument and optional substatements.
func S(k string, ss ...*Statement) *Statement {
	return &Statement{
		Keyword:    k,
		statements: ss,
	}
}

func TestParse(t *testing.T) {
	for _, tt := range []struct {
		line int
		in   string
		out  []*Statement
		err  string
	}{
		{line: line()},
		{line: line(), in: `
foo;
`,
			out: []*Statement{
				S("foo"),
			},
		},
		{line: line(), in: `
foo {}
`,
			out: []*Statement{
				S("foo"),
			},
		},
		{line: line(), in: `
foo "";
`,
			out: []*Statement{
				SA("foo", ""),
			},
		},
		{line: line(), in: `
foo bar;
`,
			out: []*Statement{
				SA("foo", "bar"),
			},
		},
		{line: line(), in: `
foo "bar";
`,
			out: []*Statement{
				SA("foo", "bar"),
			},
		},
		{line: line(), in: `
foo "\\ \S \n";
`,
			err: `test.yang:2:5: invalid escape sequence: \S`,
		},
		{line: line(), in: `
pattern "\\ \S \n";
`,
			out: []*Statement{
				SA("pattern", `\ \S
`),
			},
		},
		{line: line(), in: `
foo '\\ \S \n';
`,
			out: []*Statement{
				SA("foo", `\\ \S \n`),
			},
		},
		{line: line(), in: `
pattern '\\ \S \n';
`,
			out: []*Statement{
				SA("pattern", `\\ \S \n`),
			},
		},
		{line: line(), in: `
foo bar;
red black;
`,
			out: []*Statement{
				SA("foo", "bar"),
				SA("red", "black"),
			},
		},
		{line: line(), in: `
foo {
   key value;
}
`,
			out: []*Statement{
				S("foo",
					SA("key", "value"),
				),
			},
		},
		{line: line(), in: `
foo {
   key value;
}
`,
			out: []*Statement{
				S("foo",
					SA("key", "value"),
				),
			},
		},
		{line: line(), in: `
foo {
   key "value1     value2

	 value3";
}
`,
			out: []*Statement{
				S("foo",
					SA("key", "value1     value2\n\n value3"),
				),
			},
		},
		{line: line(), in: `
foo {
   key value;
   key2;
}
`,
			out: []*Statement{
				S("foo",
					SA("key", "value"),
					S("key2"),
				),
			},
		},
		{line: line(), in: `
foo1 {
   key value1;
}
foo2 {
   key value2;
}
foo3 value3;
`,
			out: []*Statement{
				S("foo1",
					SA("key", "value1"),
				),
				S("foo2",
					SA("key", "value2"),
				),
				SA("foo3", "value3"),
			},
		},
		{line: line(), in: `
foo1 {
    key value1;
    foo2 {
        key value2;
    }
}
`,
			out: []*Statement{
				S("foo1",
					SA("key", "value1"),
					S("foo2",
						SA("key", "value2"),
					),
				),
			},
		},
		{line: line(), in: `
 }
`,
			err: `test.yang:2:2: unexpected }`,
		},
		{line: line(), in: `
id
`,
			err: `test.yang: unexpected EOF`,
		},
		{line: line(), in: `
   {
`,
			err: `test.yang:2:4: {: not an identifier`,
		},
		{line: line(), in: `
;
`,
			err: `test.yang:2:1: ;: not an identifier`,
		},
		{line: line(), in: `
statement one two { }
`,
			err: `test.yang:2:15: two: syntax error
test.yang:2:19: {: not an identifier
test.yang:2:21: unexpected }`,
		},
		{line: line(), in: `
    }
foo {
	key: "value";
}
`,
			err: `test.yang:2:5: unexpected }`,
		},
		{line: line(), in: `
{
	something: "bad";
}
foo {
	key: "\Value";
	key2: "value2";
	bar {
		key3: "value\3;
	}
}`,
			err: `test.yang:2:1: {: not an identifier
test.yang:4:1: unexpected }
test.yang:6:7: invalid escape sequence: \V
test.yang:9:9: invalid escape sequence: \3
test.yang:9:9: missing closing "
test.yang: unexpected EOF`,
		},
	} {
		s, err := Parse(tt.in, "test.yang")
		if (s == nil) != (tt.out == nil) {
			if s == nil {
				t.Errorf("%d: did not get expected statements: %s", tt.line, tt.out)
			} else {
				t.Errorf("%d: get unexpected statements: %s", tt.line, s)
			}
		}
		switch {
		case err == nil && tt.err == "":
		case tt.err == "":
			t.Errorf("%d: unexpected error %v", tt.line, err)
			continue
		case err == nil:
			t.Errorf("%d: did not get expected error %v", tt.line, tt.err)
			continue
		case err.Error() == tt.err:
			continue
		default:
			t.Errorf("%d: got error:\n%s\nwant:\n%s", tt.line, err, tt.err)
			continue
		}
		s1 := &Statement{statements: s}
		s2 := &Statement{statements: tt.out}
		if !s1.equal(s2) {
			t.Errorf("%d: got:\n%s\nwant:\n%s", tt.line, s1, s2)
		}
	}
}

func TestWrite(t *testing.T) {
Testing:
	for _, tt := range []struct {
		line int
		in   string
		out  string
	}{
		{line: line(),
			in: `key arg { substatement; }`,
			out: `key "arg" {
	substatement;
}
`,
		},
		{line: line(),
			in: `key { substatement { key arg; }}`,
			out: `key {
	substatement {
		key "arg";
	}
}
`,
		},
		{line: line(),
			in: `
module base {
   namespace "urn:mod";
   prefix "base";

   typedef base-type { type int32; }

   grouping base-group {
     description
       "The base-group is used to test the
        'uses' statement below.  This description
        is here to simply include a multi-line
        string as an example of multi-line strings";
     leaf base-group-leaf {
       config false;
       type string;
     }
   }
   uses base-group;
}
`, out: `module "base" {
	namespace "urn:mod";
	prefix "base";
	typedef "base-type" {
		type "int32";
	}
	grouping "base-group" {
		description "The base-group is used to test the
		             'uses' statement below.  This description
		             is here to simply include a multi-line
		             string as an example of multi-line strings";
		leaf "base-group-leaf" {
			config "false";
			type "string";
		}
	}
	uses "base-group";
}
`,
		},
	} {
		in := tt.in
		// Run twice.  The first time we are parsing tt.in, the second
		// time we are parsing the output from the first parsing.
		for i := 0; i < 2; i++ {
			s, err := Parse(in, "test.yang")
			if err != nil {
				t.Errorf("%d: unexpected error %v", tt.line, err)
				continue Testing
			}
			if len(s) != 1 {
				t.Errorf("%d: got %d statements, expected 1", tt.line, len(s))
				continue Testing
			}
			var buf bytes.Buffer
			s[0].Write(&buf, "")
			out := buf.String()
			if out != tt.out {
				t.Errorf("%d: got:\n%swant:\n%s", tt.line, out, tt.out)
				continue Testing
			}
			in = out
		}
	}
}
<|endoftext|>


<commit_before>package client

import (
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	log "github.com/sirupsen/logrus"
)

type Client struct {
	clientID         string
	secret           string
	publicKey        string
	environment      string
	suppressWarnings bool
	timeout          time.Duration
}

func NewClient(clientID, secret, publicKey string, environment string, suppressWarnings bool, timeout time.Duration) *Client {
	// Initialize a client with credentials.
	// :param  str     client_id:          Your Plaid client ID
	// :arg    str     secret:             Your Plaid secret
	// :arg    str     public_key:         Your Plaid public key
	// :arg    str     environment:        One of ``sandbox``,
	//                                     ``development``, or ``production``.
	// :arg    bool    suppress_warnings:  Suppress Plaid warnings.
	// :arg    int     timeout:            Timeout for API requests.
	if environment == "development" && !suppressWarnings {
		log.Warn(`
                Development is not intended for production usage.
                Swap out url for https://api.plaid.com
                via Client.config before switching to production
            `)
	}
	client := &Client{
		clientID,
		secret,
		publicKey,
		environment,
		suppressWarnings,
		timeout,
	}

	return client
}

func (c *Client) Post(path string, data interface{}, respData interface{}) error {
	// Make a post request with clientID and secret key.
	postDataType := reflect.StructOf([]reflect.StructField{
		{
			Name:      "Data",
			Type:      reflect.TypeOf(data),
			Anonymous: true,
		},
		{
			Name: "ClientID",
			Type: reflect.TypeOf(c.clientID),
			Tag:  `json:"client_id"`,
		},
		{
			Name: "Secret",
			Type: reflect.TypeOf(c.secret),
			Tag:  `json:"secret"`,
		},
	})

	postData := reflect.New(postDataType).Elem()
	postData.Field(0).Set(reflect.ValueOf(data))
	postData.FieldByName("ClientID").SetString(c.clientID)
	postData.FieldByName("Secret").SetString(c.secret)

	return c.post(path, postData.Interface(), respData)
}

func (c *Client) PostPublic(path string, data interface{}, respData interface{}) error {
	// Make a post request requiring no auth.
	return c.post(path, data, respData)
}

func (c *Client) PostPublicKey(path string, data interface{}, respData interface{}) error {
	// Make a post request using a public key.
	postDataType := reflect.StructOf([]reflect.StructField{
		{
			Name:      "Data",
			Type:      reflect.TypeOf(data),
			Anonymous: true,
		},
		{
			Name: "PublicKey",
			Type: reflect.TypeOf(c.publicKey),
			Tag:  `json:"public_key"`,
		},
	})

	postData := reflect.New(postDataType).Elem()
	postData.Field(0).Set(reflect.ValueOf(data))
	postData.FieldByName("PublicKey").SetString(c.publicKey)

	return c.post(path, postData.Interface(), respData)
}

func (c *Client) post(path string, data interface{}, respData interface{}) error {
	fmt.Println("https://" + string(c.environment) + ".plaid.com" + string(path))
	json, _ := json.Marshal(data)
	fmt.Println(string(json))
	return postRequest(
		"https://"+string(c.environment)+".plaid.com"+string(path),
		data,
		c.timeout,
		respData)
}
<commit_msg>Remove print statements<commit_after>package client

import (
	"encoding/json"
	"reflect"
	"time"

	log "github.com/sirupsen/logrus"
)

type Client struct {
	clientID         string
	secret           string
	publicKey        string
	environment      string
	suppressWarnings bool
	timeout          time.Duration
}

func NewClient(clientID, secret, publicKey string, environment string, suppressWarnings bool, timeout time.Duration) *Client {
	// Initialize a client with credentials.
	// :param  str     client_id:          Your Plaid client ID
	// :arg    str     secret:             Your Plaid secret
	// :arg    str     public_key:         Your Plaid public key
	// :arg    str     environment:        One of ``sandbox``,
	//                                     ``development``, or ``production``.
	// :arg    bool    suppress_warnings:  Suppress Plaid warnings.
	// :arg    int     timeout:            Timeout for API requests.
	if environment == "development" && !suppressWarnings {
		log.Warn(`
                Development is not intended for production usage.
                Swap out url for https://api.plaid.com
                via Client.config before switching to production
            `)
	}
	client := &Client{
		clientID,
		secret,
		publicKey,
		environment,
		suppressWarnings,
		timeout,
	}

	return client
}

func (c *Client) Post(path string, data interface{}, respData interface{}) error {
	// Make a post request with clientID and secret key.
	postDataType := reflect.StructOf([]reflect.StructField{
		{
			Name:      "Data",
			Type:      reflect.TypeOf(data),
			Anonymous: true,
		},
		{
			Name: "ClientID",
			Type: reflect.TypeOf(c.clientID),
			Tag:  `json:"client_id"`,
		},
		{
			Name: "Secret",
			Type: reflect.TypeOf(c.secret),
			Tag:  `json:"secret"`,
		},
	})

	postData := reflect.New(postDataType).Elem()
	postData.Field(0).Set(reflect.ValueOf(data))
	postData.FieldByName("ClientID").SetString(c.clientID)
	postData.FieldByName("Secret").SetString(c.secret)

	return c.post(path, postData.Interface(), respData)
}

func (c *Client) PostPublic(path string, data interface{}, respData interface{}) error {
	// Make a post request requiring no auth.
	return c.post(path, data, respData)
}

func (c *Client) PostPublicKey(path string, data interface{}, respData interface{}) error {
	// Make a post request using a public key.
	postDataType := reflect.StructOf([]reflect.StructField{
		{
			Name:      "Data",
			Type:      reflect.TypeOf(data),
			Anonymous: true,
		},
		{
			Name: "PublicKey",
			Type: reflect.TypeOf(c.publicKey),
			Tag:  `json:"public_key"`,
		},
	})

	postData := reflect.New(postDataType).Elem()
	postData.Field(0).Set(reflect.ValueOf(data))
	postData.FieldByName("PublicKey").SetString(c.publicKey)

	return c.post(path, postData.Interface(), respData)
}

func (c *Client) post(path string, data interface{}, respData interface{}) error {
	json, _ := json.Marshal(data)
	return postRequest(
		"https://"+string(c.environment)+".plaid.com"+string(path),
		data,
		c.timeout,
		respData)
}
<|endoftext|>


<commit_before>package switchboard_test

import (
	. "github.com/onsi/ginkgo"
	"github.com/pivotal-cf-experimental/switchboard"
)

var _ = Describe("Backends", func() {
	var backends switchboard.Backends
	// var backend1 switchboard.Backend
	// var backend2 switchboard.Backend
	// var backend3 switchboard.Backend

	BeforeEach(func() {
		backend_ips := []string{"localhost", "localhost", "localhost"}
		backend_ports := []uint{50000, 50001, 50002}
		healthcheck_ports := []uint{60000, 60001, 60002}
		backends = switchboard.NewBackends(backend_ips, backend_ports, healthcheck_ports, nil)
	})

	Describe("Concurrent operations", func() {
		It("do not result in a race", func() {
			readySetGo := make(chan interface{})

			doneChans := []chan interface{}{
				make(chan interface{}),
				make(chan interface{}),
				make(chan interface{}),
			}

			go func() {
				<-readySetGo
				backends.All()
				close(doneChans[0])
			}()

			go func() {
				<-readySetGo
				backends.Active()
				close(doneChans[1])
			}()

			go func() {
				<-readySetGo
				backends.SetActive(nil)
				close(doneChans[2])
			}()

			close(readySetGo)

			for _, done := range doneChans {
				<-done
			}
		})
	})

	// Describe("Remove", func() {
	//   It("removes only the given bridge", func() {
	//     err := bridges.Remove(bridge2)
	//     Expect(err).NotTo(HaveOccurred())

	//     Expect(bridges.Contains(bridge1)).To(BeTrue())
	//     Expect(bridges.Contains(bridge2)).To(BeFalse())
	//     Expect(bridges.Contains(bridge3)).To(BeTrue())

	//     Expect(bridges.Size()).To(Equal(2))
	//   })

	//   Context("when the bridge cannot be found", func() {
	//     It("returns an error", func() {
	//       err := bridges.Remove(switchboard.NewBridge(&fakes.FakeReadWriteCloser{}, &fakes.FakeReadWriteCloser{}, lager.NewLogger("test")))
	//       Expect(err).To(HaveOccurred())
	//       Expect(err).To(MatchError("Bridge not found"))
	//     })
	//   })
	// })

	// Describe("RemoveAndCloseAll", func() {
	//   BeforeEach(func() {
	//     switchboard.BridgeProvider = func(_, _ io.ReadWriteCloser, _ lager.Logger) switchboard.Bridge {
	//       return &fakes.FakeBridge{}
	//     }
	//   })

	//   AfterEach(func() {
	//     switchboard.BridgeProvider = switchboard.NewBridge
	//   })

	//   It("closes all bridges", func() {
	//     bridges.RemoveAndCloseAll()

	//     Expect(bridge1.(*fakes.FakeBridge).CloseCallCount()).To(Equal(1))
	//     Expect(bridge2.(*fakes.FakeBridge).CloseCallCount()).To(Equal(1))
	//     Expect(bridge3.(*fakes.FakeBridge).CloseCallCount()).To(Equal(1))
	//   })

	//   It("removes all bridges", func() {
	//     bridges.RemoveAndCloseAll()

	//     Expect(bridges.Size()).To(Equal(0))
	//   })
	// })
})
<commit_msg>Filled in rest of backends tests for Active/SetActive/All<commit_after>package switchboard_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pivotal-cf-experimental/switchboard"
)

var _ = Describe("Backends", func() {
	var (
		backends          switchboard.Backends
		backend_ips       []string
		backend_ports     []uint
		healthcheck_ports []uint
	)

	BeforeEach(func() {
		backend_ips = []string{"localhost", "localhost", "localhost"}
		backend_ports = []uint{50000, 50001, 50002}
		healthcheck_ports = []uint{60000, 60001, 60002}
		backends = switchboard.NewBackends(backend_ips, backend_ports, healthcheck_ports, nil)
	})

	Describe("Concurrent operations", func() {
		It("do not result in a race", func() {
			readySetGo := make(chan interface{})

			doneChans := []chan interface{}{
				make(chan interface{}),
				make(chan interface{}),
				make(chan interface{}),
			}

			go func() {
				<-readySetGo
				backends.All()
				close(doneChans[0])
			}()

			go func() {
				<-readySetGo
				backends.Active()
				close(doneChans[1])
			}()

			go func() {
				<-readySetGo
				backends.SetActive(nil)
				close(doneChans[2])
			}()

			close(readySetGo)

			for _, done := range doneChans {
				<-done
			}
		})
	})

	Describe("All", func() {
		It("returns a constant list of backends", func() {
			i := 0
			for backend := range backends.All() {
				currentBackend := switchboard.NewBackend(backend_ips[i], backend_ports[i], healthcheck_ports[i], nil)
				i++
				Expect(currentBackend).To(Equal(backend))
			}
		})
	})

	Describe("Active", func() {
		It("returns the currently active backend", func() {
			currentActive := switchboard.NewBackend(backend_ips[0], backend_ports[0], healthcheck_ports[0], nil)
			Expect(currentActive).To(Equal(backends.Active()))
		})
	})

	Describe("SetActive", func() {
		var backend switchboard.Backend
		var active switchboard.Backend

		BeforeEach(func() {
			active = backends.Active()

			for b := range backends.All() {
				if b != active {
					backend = b
					break
				}
			}
		})

		It("sets the active backend", func() {
			Expect(backends.SetActive(backend)).NotTo(HaveOccurred())
			Expect(backends.Active()).To(Equal(backend))

		})
	})

	// Describe("Remove", func() {
	//   It("removes only the given bridge", func() {
	//     err := bridges.Remove(bridge2)
	//     Expect(err).NotTo(HaveOccurred())

	//     Expect(bridges.Contains(bridge1)).To(BeTrue())
	//     Expect(bridges.Contains(bridge2)).To(BeFalse())
	//     Expect(bridges.Contains(bridge3)).To(BeTrue())

	//     Expect(bridges.Size()).To(Equal(2))
	//   })

	//   Context("when the bridge cannot be found", func() {
	//     It("returns an error", func() {
	//       err := bridges.Remove(switchboard.NewBridge(&fakes.FakeReadWriteCloser{}, &fakes.FakeReadWriteCloser{}, lager.NewLogger("test")))
	//       Expect(err).To(HaveOccurred())
	//       Expect(err).To(MatchError("Bridge not found"))
	//     })
	//   })
	// })

	// Describe("RemoveAndCloseAll", func() {
	//   BeforeEach(func() {
	//     switchboard.BridgeProvider = func(_, _ io.ReadWriteCloser, _ lager.Logger) switchboard.Bridge {
	//       return &fakes.FakeBridge{}
	//     }
	//   })

	//   AfterEach(func() {
	//     switchboard.BridgeProvider = switchboard.NewBridge
	//   })

	//   It("closes all bridges", func() {
	//     bridges.RemoveAndCloseAll()

	//     Expect(bridge1.(*fakes.FakeBridge).CloseCallCount()).To(Equal(1))
	//     Expect(bridge2.(*fakes.FakeBridge).CloseCallCount()).To(Equal(1))
	//     Expect(bridge3.(*fakes.FakeBridge).CloseCallCount()).To(Equal(1))
	//   })

	//   It("removes all bridges", func() {
	//     bridges.RemoveAndCloseAll()

	//     Expect(bridges.Size()).To(Equal(0))
	//   })
	// })
})
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package mobileproxy instantiates a Martian Proxy.
// This package is a reference implementation of Martian Proxy intended to
// be cross compiled with gomobile for use on Android and iOS.
package mobileproxy

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net"
	"net/http"
	"path"
	"time"

	"github.com/google/martian"
	"github.com/google/martian/api"
	"github.com/google/martian/cybervillains"
	"github.com/google/martian/fifo"
	"github.com/google/martian/har"
	"github.com/google/martian/httpspec"
	mlog "github.com/google/martian/log"
	"github.com/google/martian/martianhttp"
	"github.com/google/martian/mitm"
	"github.com/google/martian/servemux"
	"github.com/google/martian/verify"

	// side-effect importing to register with JSON API
	_ "github.com/google/martian/body"
	_ "github.com/google/martian/cookie"
	_ "github.com/google/martian/header"
	_ "github.com/google/martian/martianurl"
	_ "github.com/google/martian/method"
	_ "github.com/google/martian/pingback"
	_ "github.com/google/martian/port"
	_ "github.com/google/martian/priority"
	_ "github.com/google/martian/querystring"
	_ "github.com/google/martian/skip"
	_ "github.com/google/martian/stash"
	_ "github.com/google/martian/static"
	_ "github.com/google/martian/status"
)

var started = false

// Martian is a wrapper for the initialized Martian proxy
type Martian struct {
	proxy    *martian.Proxy
	listener net.Listener
	mux      *http.ServeMux
}

// Start runs a martian.Proxy on trafficPort and the API server on apiPort.
func Start(trafficPort, apiPort int) (*Martian, error) {
	return StartWithCertificate(trafficPort, apiPort, "", "")
}

// StartWithCyberVillains runs a martian.Proxy on trafficPort and the API
// server on apiPort configured to perform MITM with the CyberVillains cert and key.
func StartWithCyberVillains(trafficPort int, apiPort int) (*Martian, error) {
	return StartWithCertificate(trafficPort, apiPort, cybervillains.Cert, cybervillains.Key)
}

// StartWithCertificate runs a martian.Proxy on trafficPort and the API
// server on apiPort configured to perform MITM with the cert and key provided.
func StartWithCertificate(trafficPort int, apiPort int, cert, key string) (*Martian, error) {
	var err error
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", trafficPort))
	if err != nil {
		log.Fatal(err)
	}

	mlog.Debugf("mobileproxy: started listener on: %v", listener.Addr())
	proxy := martian.NewProxy()
	mux := http.NewServeMux()

	if cert != "" && key != "" {
		tlsc, err := tls.X509KeyPair([]byte(cert), []byte(key))
		if err != nil {
			log.Fatal(err)
		}

		mlog.Debugf("mobileproxy: loaded cert and key")

		x509c, err := x509.ParseCertificate(tlsc.Certificate[0])
		if err != nil {
			log.Fatal(err)
		}

		mlog.Debugf("mobileproxy: parsed cert")

		mc, err := mitm.NewConfig(x509c, tlsc.PrivateKey)
		if err != nil {
			log.Fatal(err)
		}

		mc.SetValidity(12 * time.Hour)
		mc.SetOrganization("Martian Proxy")

		proxy.SetMITM(mc)

		handle(mux, "/authority.cer", apiPort, martianhttp.NewAuthorityHandler(x509c))
	}

	// Forward traffic that pattern matches in http.DefaultServeMux before applying
	// httpspec modifiers (via modifier, specifically)
	topg := fifo.NewGroup()
	apif := servemux.NewFilter(nil)
	apif.SetRequestModifier(api.NewForwarder("", apiPort))
	topg.AddRequestModifier(apif)

	stack, fg := httpspec.NewStack("martian.mobileproxy")
	topg.AddRequestModifier(stack)
	topg.AddResponseModifier(stack)

	proxy.SetRequestModifier(topg)
	proxy.SetResponseModifier(topg)

	// add HAR logger for unmodified logs.
	uhl := har.NewLogger()
	fg.AddRequestModifier(uhl)
	fg.AddResponseModifier(uhl)

	// add HAR logger
	hl := har.NewLogger()
	stack.AddRequestModifier(hl)
	stack.AddResponseModifier(hl)

	m := martianhttp.NewModifier()
	fg.AddRequestModifier(m)
	fg.AddResponseModifier(m)

	// Proxy specific handlers.
	// These handlers take precendence over proxy traffic and will not be intercepted.

	// Update modifiers.
	handle(mux, "/configure", apiPort, m)
	mlog.Infof("mobileproxy: configure with requests to http://martian.proxy/configure")

	// Retrieve Unmodified HAR logs
	handle(mux, "/logs/original", apiPort, har.NewExportHandler(uhl))
	handle(mux, "/logs/original/reset", apiPort, har.NewResetHandler(uhl))

	// Retrieve HAR logs
	handle(mux, "/logs", apiPort, har.NewExportHandler(hl))
	handle(mux, "/logs/reset", apiPort, har.NewResetHandler(hl))

	// Verify assertions.
	vh := verify.NewHandler()
	vh.SetRequestVerifier(m)
	vh.SetResponseVerifier(m)

	handle(mux, "/verify", apiPort, vh)
	mlog.Infof("mobileproxy: check verifications with requests to http://martian.proxy/verify")

	// Reset verifications.
	rh := verify.NewResetHandler()
	rh.SetRequestVerifier(m)
	rh.SetResponseVerifier(m)
	handle(mux, "/verify/reset", apiPort, rh)
	mlog.Infof("mobileproxy: reset verifications with requests to http://martian.proxy/verify/reset")

	mlog.Infof("mobileproxy: starting Martian proxy on listener")
	go proxy.Serve(listener)

	// start the API server
	apiAddr := fmt.Sprintf(":%d", apiPort)
	go http.ListenAndServe(apiAddr, mux)
	mlog.Infof("mobileproxy: proxy API started on %s", apiAddr)
	started = true

	return &Martian{
		proxy:    proxy,
		listener: listener,
		mux:      mux,
	}, nil
}

// IsStarted returns true if the proxy has finished starting up.
func IsStarted() bool {
	return started
}

// Shutdown tells the Proxy to close. The proxy will stay alive until all connections through it
// have closed or timed out.
func (p *Martian) Shutdown() {
	mlog.Infof("mobileproxy: shutting down proxy")
	p.listener.Close()
	p.proxy.Close()
	started = false
	mlog.Infof("mobileproxy: proxy shut down")
}

// SetLogLevel sets the Martian log level (Silent = 0, Error, Info, Debug), controlling which Martian
// log calls are displayed in the console
func SetLogLevel(l int) {
	mlog.SetLevel(l)
}

// handle sets up http.DefaultServeMux to handle requests to match patterns martian.proxy/{pth} and
// localhost:{apiPort}/{pth}. This assumes that the API server is running at localhost:{apiPort}, and
// requests to martian.proxy are forwarded there.
func handle(mux *http.ServeMux, pattern string, apiPort int, handler http.Handler) {
	mux.Handle(pattern, handler)
	mlog.Infof("mobileproxy: handler registered for %s", pattern)

	lhp := path.Join(fmt.Sprintf("localhost:%d", apiPort), pattern)
	mux.Handle(lhp, handler)
	mlog.Infof("mobileproxy: handler registered for %s", lhp)
}
<commit_msg>Adds CORS support to mobileproxy (#155)<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package mobileproxy instantiates a Martian Proxy.
// This package is a reference implementation of Martian Proxy intended to
// be cross compiled with gomobile for use on Android and iOS.
package mobileproxy

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net"
	"net/http"
	"path"
	"time"

	"github.com/google/martian"
	"github.com/google/martian/api"
	"github.com/google/martian/cors"
	"github.com/google/martian/cybervillains"
	"github.com/google/martian/fifo"
	"github.com/google/martian/har"
	"github.com/google/martian/httpspec"
	mlog "github.com/google/martian/log"
	"github.com/google/martian/martianhttp"
	"github.com/google/martian/mitm"
	"github.com/google/martian/servemux"
	"github.com/google/martian/verify"

	// side-effect importing to register with JSON API
	_ "github.com/google/martian/body"
	_ "github.com/google/martian/cookie"
	_ "github.com/google/martian/header"
	_ "github.com/google/martian/martianurl"
	_ "github.com/google/martian/method"
	_ "github.com/google/martian/pingback"
	_ "github.com/google/martian/port"
	_ "github.com/google/martian/priority"
	_ "github.com/google/martian/querystring"
	_ "github.com/google/martian/skip"
	_ "github.com/google/martian/stash"
	_ "github.com/google/martian/static"
	_ "github.com/google/martian/status"
)

var started = false

// Martian is a wrapper for the initialized Martian proxy
type Martian struct {
	proxy    *martian.Proxy
	listener net.Listener
	mux      *http.ServeMux
}

// Start runs a martian.Proxy on trafficPort and the API server on apiPort,
// with the allowsCORS flag set to false.
func Start(trafficPort, apiPort int) (*Martian, error) {
	return StartWithCertificate(trafficPort, apiPort, "", "")
}

// StartWithCyberVillains runs a martian.Proxy on trafficPort and the API
// server on apiPort configured to perform MITM with the CyberVillains cert and key,
// with the allowsCors flag set to false.
func StartWithCyberVillains(trafficPort int, apiPort int) (*Martian, error) {
	return StartWithCertificate(trafficPort, apiPort, cybervillains.Cert, cybervillains.Key)
}

// Start runs a martian.Proxy on trafficPort and the API server on apiPort,
// with the allowsCORS flag set to true.
func StartWithCORS(trafficPort, apiPort int) (*Martian, error) {
	return StartWithCertificateCORS(trafficPort, apiPort, "", "", true)
}

// StartWithCertificate runs a martian.Proxy on trafficPort and the API
// server on apiPort configured to perform MITM with the cert and key provided,
// with the allowsCORS flag set to false.
func StartWithCertificate(trafficPort int, apiPort int, cert, key string) (*Martian, error) {
	return StartWithCertificateCORS(trafficPort, apiPort, cert, key, false)
}

// StartWithCertificate runs a martian.Proxy on trafficPort and the API
// server on apiPort configured to perform MITM with the cert and key provided,
// and the allowCORS flag set if instructed.
func StartWithCertificateCORS(trafficPort int, apiPort int, cert, key string, allowsCors bool) (*Martian, error) {
	var err error
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", trafficPort))
	if err != nil {
		log.Fatal(err)
	}

	mlog.Debugf("mobileproxy: started listener on: %v", listener.Addr())
	proxy := martian.NewProxy()
	mux := http.NewServeMux()

	if cert != "" && key != "" {
		tlsc, err := tls.X509KeyPair([]byte(cert), []byte(key))
		if err != nil {
			log.Fatal(err)
		}

		mlog.Debugf("mobileproxy: loaded cert and key")

		x509c, err := x509.ParseCertificate(tlsc.Certificate[0])
		if err != nil {
			log.Fatal(err)
		}

		mlog.Debugf("mobileproxy: parsed cert")

		mc, err := mitm.NewConfig(x509c, tlsc.PrivateKey)
		if err != nil {
			log.Fatal(err)
		}

		mc.SetValidity(12 * time.Hour)
		mc.SetOrganization("Martian Proxy")

		proxy.SetMITM(mc)

		handle(mux, "/authority.cer", apiPort, martianhttp.NewAuthorityHandler(x509c), allowsCors)
	}

	// Forward traffic that pattern matches in http.DefaultServeMux before applying
	// httpspec modifiers (via modifier, specifically)
	topg := fifo.NewGroup()
	apif := servemux.NewFilter(nil)
	apif.SetRequestModifier(api.NewForwarder("", apiPort))
	topg.AddRequestModifier(apif)

	stack, fg := httpspec.NewStack("martian.mobileproxy")
	topg.AddRequestModifier(stack)
	topg.AddResponseModifier(stack)

	proxy.SetRequestModifier(topg)
	proxy.SetResponseModifier(topg)

	// add HAR logger for unmodified logs.
	uhl := har.NewLogger()
	fg.AddRequestModifier(uhl)
	fg.AddResponseModifier(uhl)

	// add HAR logger
	hl := har.NewLogger()
	stack.AddRequestModifier(hl)
	stack.AddResponseModifier(hl)

	m := martianhttp.NewModifier()
	fg.AddRequestModifier(m)
	fg.AddResponseModifier(m)

	// Proxy specific handlers.
	// These handlers take precendence over proxy traffic and will not be intercepted.

	// Update modifiers.
	handle(mux, "/configure", apiPort, m, allowsCors)
	mlog.Infof("mobileproxy: configure with requests to http://martian.proxy/configure")

	// Retrieve Unmodified HAR logs
	handle(mux, "/logs/original", apiPort, har.NewExportHandler(uhl), allowsCors)
	handle(mux, "/logs/original/reset", apiPort, har.NewResetHandler(uhl), allowsCors)

	// Retrieve HAR logs
	handle(mux, "/logs", apiPort, har.NewExportHandler(hl), allowsCors)
	handle(mux, "/logs/reset", apiPort, har.NewResetHandler(hl), allowsCors)

	// Verify assertions.
	vh := verify.NewHandler()
	vh.SetRequestVerifier(m)
	vh.SetResponseVerifier(m)

	handle(mux, "/verify", apiPort, vh, allowsCors)
	mlog.Infof("mobileproxy: check verifications with requests to http://martian.proxy/verify")

	// Reset verifications.
	rh := verify.NewResetHandler()
	rh.SetRequestVerifier(m)
	rh.SetResponseVerifier(m)
	handle(mux, "/verify/reset", apiPort, rh, allowsCors)
	mlog.Infof("mobileproxy: reset verifications with requests to http://martian.proxy/verify/reset")

	mlog.Infof("mobileproxy: starting Martian proxy on listener")
	go proxy.Serve(listener)

	// start the API server
	apiAddr := fmt.Sprintf(":%d", apiPort)
	go http.ListenAndServe(apiAddr, mux)
	mlog.Infof("mobileproxy: proxy API started on %s", apiAddr)
	started = true

	return &Martian{
		proxy:    proxy,
		listener: listener,
		mux:      mux,
	}, nil
}

// IsStarted returns true if the proxy has finished starting up.
func IsStarted() bool {
	return started
}

// Shutdown tells the Proxy to close. The proxy will stay alive until all connections through it
// have closed or timed out.
func (p *Martian) Shutdown() {
	mlog.Infof("mobileproxy: shutting down proxy")
	p.listener.Close()
	p.proxy.Close()
	started = false
	mlog.Infof("mobileproxy: proxy shut down")
}

// SetLogLevel sets the Martian log level (Silent = 0, Error, Info, Debug), controlling which Martian
// log calls are displayed in the console
func SetLogLevel(l int) {
	mlog.SetLevel(l)
}

// handle sets up http.DefaultServeMux to handle requests to match patterns martian.proxy/{pth} and
// localhost:{apiPort}/{pth}. This assumes that the API server is running at localhost:{apiPort}, and
// requests to martian.proxy are forwarded there.
func handle(mux *http.ServeMux, pattern string, apiPort int, handler http.Handler, allowsCors bool) {
	if allowsCors == true {
		handler = cors.NewHandler(handler)
	}
	mux.Handle(pattern, handler)
	mlog.Infof("mobileproxy: handler registered for %s", pattern)

	lhp := path.Join(fmt.Sprintf("localhost:%d", apiPort), pattern)
	mux.Handle(lhp, handler)
	mlog.Infof("mobileproxy: handler registered for %s", lhp)
}

<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package x509

import (
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"fmt"
	"net/http"
	"strings"
	"time"

	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/component-base/metrics"
	"k8s.io/component-base/metrics/legacyregistry"
)

/*
 * By default, the following metric is defined as falling under
 * ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
 *
 * Promoting the stability level of the metric is a responsibility of the component owner, since it
 * involves explicitly acknowledging support for the metric across multiple releases, in accordance with
 * the metric stability policy.
 */
var clientCertificateExpirationHistogram = metrics.NewHistogram(
	&metrics.HistogramOpts{
		Namespace: "apiserver",
		Subsystem: "client",
		Name:      "certificate_expiration_seconds",
		Help:      "Distribution of the remaining lifetime on the certificate used to authenticate a request.",
		Buckets: []float64{
			0,
			(30 * time.Minute).Seconds(),
			(1 * time.Hour).Seconds(),
			(2 * time.Hour).Seconds(),
			(6 * time.Hour).Seconds(),
			(12 * time.Hour).Seconds(),
			(24 * time.Hour).Seconds(),
			(2 * 24 * time.Hour).Seconds(),
			(4 * 24 * time.Hour).Seconds(),
			(7 * 24 * time.Hour).Seconds(),
			(30 * 24 * time.Hour).Seconds(),
			(3 * 30 * 24 * time.Hour).Seconds(),
			(6 * 30 * 24 * time.Hour).Seconds(),
			(12 * 30 * 24 * time.Hour).Seconds(),
		},
		StabilityLevel: metrics.ALPHA,
	},
)

func init() {
	legacyregistry.MustRegister(clientCertificateExpirationHistogram)
}

// UserConversion defines an interface for extracting user info from a client certificate chain
type UserConversion interface {
	User(chain []*x509.Certificate) (*authenticator.Response, bool, error)
}

// UserConversionFunc is a function that implements the UserConversion interface.
type UserConversionFunc func(chain []*x509.Certificate) (*authenticator.Response, bool, error)

// User implements x509.UserConversion
func (f UserConversionFunc) User(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
	return f(chain)
}

func columnSeparatedHex(d []byte) string {
	h := strings.ToUpper(hex.EncodeToString(d))
	var sb strings.Builder
	for i, r := range h {
		sb.WriteRune(r)
		if i%2 == 1 && i != len(h)-1 {
			sb.WriteRune(':')
		}
	}
	return sb.String()
}

func certificateIdentifier(c *x509.Certificate) string {
	return fmt.Sprintf(
		"SN=%d, SKID=%s, AKID=%s",
		c.SerialNumber,
		columnSeparatedHex(c.SubjectKeyId),
		columnSeparatedHex(c.AuthorityKeyId),
	)
}

// VerifyOptionFunc is function which provides a shallow copy of the VerifyOptions to the authenticator.  This allows
// for cases where the options (particularly the CAs) can change.  If the bool is false, then the returned VerifyOptions
// are ignored and the authenticator will express "no opinion".  This allows a clear signal for cases where a CertPool
// is eventually expected, but not currently present.
type VerifyOptionFunc func() (x509.VerifyOptions, bool)

// Authenticator implements request.Authenticator by extracting user info from verified client certificates
type Authenticator struct {
	verifyOptionsFn VerifyOptionFunc
	user            UserConversion
}

// New returns a request.Authenticator that verifies client certificates using the provided
// VerifyOptions, and converts valid certificate chains into user.Info using the provided UserConversion
func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
	return NewDynamic(StaticVerifierFn(opts), user)
}

// NewDynamic returns a request.Authenticator that verifies client certificates using the provided
// VerifyOptionFunc (which may be dynamic), and converts valid certificate chains into user.Info using the provided UserConversion
func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator {
	return &Authenticator{verifyOptionsFn, user}
}

// AuthenticateRequest authenticates the request using presented client certificates
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
	if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
		return nil, false, nil
	}

	// Use intermediates, if provided
	optsCopy, ok := a.verifyOptionsFn()
	// if there are intentionally no verify options, then we cannot authenticate this request
	if !ok {
		return nil, false, nil
	}
	if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
		optsCopy.Intermediates = x509.NewCertPool()
		for _, intermediate := range req.TLS.PeerCertificates[1:] {
			optsCopy.Intermediates.AddCert(intermediate)
		}
	}

	remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
	clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds())
	chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
	if err != nil {
		return nil, false, fmt.Errorf(
			"verifying certificate %s failed: %w",
			certificateIdentifier(req.TLS.PeerCertificates[0]),
			err,
		)
	}

	var errlist []error
	for _, chain := range chains {
		user, ok, err := a.user.User(chain)
		if err != nil {
			errlist = append(errlist, err)
			continue
		}

		if ok {
			return user, ok, err
		}
	}
	return nil, false, utilerrors.NewAggregate(errlist)
}

// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
type Verifier struct {
	verifyOptionsFn VerifyOptionFunc
	auth            authenticator.Request

	// allowedCommonNames contains the common names which a verified certificate is allowed to have.
	// If empty, all verified certificates are allowed.
	allowedCommonNames StringSliceProvider
}

// NewVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request {
	return NewDynamicCAVerifier(StaticVerifierFn(opts), auth, StaticStringSlice(allowedCommonNames.List()))
}

// NewDynamicCAVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
func NewDynamicCAVerifier(verifyOptionsFn VerifyOptionFunc, auth authenticator.Request, allowedCommonNames StringSliceProvider) authenticator.Request {
	return &Verifier{verifyOptionsFn, auth, allowedCommonNames}
}

// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
	if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
		return nil, false, nil
	}

	// Use intermediates, if provided
	optsCopy, ok := a.verifyOptionsFn()
	// if there are intentionally no verify options, then we cannot authenticate this request
	if !ok {
		return nil, false, nil
	}
	if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
		optsCopy.Intermediates = x509.NewCertPool()
		for _, intermediate := range req.TLS.PeerCertificates[1:] {
			optsCopy.Intermediates.AddCert(intermediate)
		}
	}

	if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
		return nil, false, err
	}
	if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
		return nil, false, err
	}
	return a.auth.AuthenticateRequest(req)
}

func (a *Verifier) verifySubject(subject pkix.Name) error {
	// No CN restrictions
	if len(a.allowedCommonNames.Value()) == 0 {
		return nil
	}
	// Enforce CN restrictions
	for _, allowedCommonName := range a.allowedCommonNames.Value() {
		if allowedCommonName == subject.CommonName {
			return nil
		}
	}
	return fmt.Errorf("x509: subject with cn=%s is not in the allowed list", subject.CommonName)
}

// DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
// and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth)
func DefaultVerifyOptions() x509.VerifyOptions {
	return x509.VerifyOptions{
		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
}

// CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
	if len(chain[0].Subject.CommonName) == 0 {
		return nil, false, nil
	}
	return &authenticator.Response{
		User: &user.DefaultInfo{
			Name:   chain[0].Subject.CommonName,
			Groups: chain[0].Subject.Organization,
		},
	}, true, nil
})
<commit_msg>actually resolve the computations for buckets for static analysis<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package x509

import (
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"fmt"
	"net/http"
	"strings"
	"time"

	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/component-base/metrics"
	"k8s.io/component-base/metrics/legacyregistry"
)

/*
 * By default, the following metric is defined as falling under
 * ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
 *
 * Promoting the stability level of the metric is a responsibility of the component owner, since it
 * involves explicitly acknowledging support for the metric across multiple releases, in accordance with
 * the metric stability policy.
 */
var clientCertificateExpirationHistogram = metrics.NewHistogram(
	&metrics.HistogramOpts{
		Namespace: "apiserver",
		Subsystem: "client",
		Name:      "certificate_expiration_seconds",
		Help:      "Distribution of the remaining lifetime on the certificate used to authenticate a request.",
		Buckets: []float64{
			0,
			1800,     // 30 minutes
			3600,     // 1 hour
			7200,     // 2 hours
			21600,    // 6 hours
			43200,    // 12 hours
			86400,    // 1 day
			172800,   // 2 days
			345600,   // 4 days
			604800,   // 1 week
			2592000,  // 1 month
			7776000,  // 3 months
			15552000, // 6 months
			31104000, // 1 year
		},
		StabilityLevel: metrics.ALPHA,
	},
)

func init() {
	legacyregistry.MustRegister(clientCertificateExpirationHistogram)
}

// UserConversion defines an interface for extracting user info from a client certificate chain
type UserConversion interface {
	User(chain []*x509.Certificate) (*authenticator.Response, bool, error)
}

// UserConversionFunc is a function that implements the UserConversion interface.
type UserConversionFunc func(chain []*x509.Certificate) (*authenticator.Response, bool, error)

// User implements x509.UserConversion
func (f UserConversionFunc) User(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
	return f(chain)
}

func columnSeparatedHex(d []byte) string {
	h := strings.ToUpper(hex.EncodeToString(d))
	var sb strings.Builder
	for i, r := range h {
		sb.WriteRune(r)
		if i%2 == 1 && i != len(h)-1 {
			sb.WriteRune(':')
		}
	}
	return sb.String()
}

func certificateIdentifier(c *x509.Certificate) string {
	return fmt.Sprintf(
		"SN=%d, SKID=%s, AKID=%s",
		c.SerialNumber,
		columnSeparatedHex(c.SubjectKeyId),
		columnSeparatedHex(c.AuthorityKeyId),
	)
}

// VerifyOptionFunc is function which provides a shallow copy of the VerifyOptions to the authenticator.  This allows
// for cases where the options (particularly the CAs) can change.  If the bool is false, then the returned VerifyOptions
// are ignored and the authenticator will express "no opinion".  This allows a clear signal for cases where a CertPool
// is eventually expected, but not currently present.
type VerifyOptionFunc func() (x509.VerifyOptions, bool)

// Authenticator implements request.Authenticator by extracting user info from verified client certificates
type Authenticator struct {
	verifyOptionsFn VerifyOptionFunc
	user            UserConversion
}

// New returns a request.Authenticator that verifies client certificates using the provided
// VerifyOptions, and converts valid certificate chains into user.Info using the provided UserConversion
func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
	return NewDynamic(StaticVerifierFn(opts), user)
}

// NewDynamic returns a request.Authenticator that verifies client certificates using the provided
// VerifyOptionFunc (which may be dynamic), and converts valid certificate chains into user.Info using the provided UserConversion
func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator {
	return &Authenticator{verifyOptionsFn, user}
}

// AuthenticateRequest authenticates the request using presented client certificates
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
	if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
		return nil, false, nil
	}

	// Use intermediates, if provided
	optsCopy, ok := a.verifyOptionsFn()
	// if there are intentionally no verify options, then we cannot authenticate this request
	if !ok {
		return nil, false, nil
	}
	if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
		optsCopy.Intermediates = x509.NewCertPool()
		for _, intermediate := range req.TLS.PeerCertificates[1:] {
			optsCopy.Intermediates.AddCert(intermediate)
		}
	}

	remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
	clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds())
	chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
	if err != nil {
		return nil, false, fmt.Errorf(
			"verifying certificate %s failed: %w",
			certificateIdentifier(req.TLS.PeerCertificates[0]),
			err,
		)
	}

	var errlist []error
	for _, chain := range chains {
		user, ok, err := a.user.User(chain)
		if err != nil {
			errlist = append(errlist, err)
			continue
		}

		if ok {
			return user, ok, err
		}
	}
	return nil, false, utilerrors.NewAggregate(errlist)
}

// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
type Verifier struct {
	verifyOptionsFn VerifyOptionFunc
	auth            authenticator.Request

	// allowedCommonNames contains the common names which a verified certificate is allowed to have.
	// If empty, all verified certificates are allowed.
	allowedCommonNames StringSliceProvider
}

// NewVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request {
	return NewDynamicCAVerifier(StaticVerifierFn(opts), auth, StaticStringSlice(allowedCommonNames.List()))
}

// NewDynamicCAVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
func NewDynamicCAVerifier(verifyOptionsFn VerifyOptionFunc, auth authenticator.Request, allowedCommonNames StringSliceProvider) authenticator.Request {
	return &Verifier{verifyOptionsFn, auth, allowedCommonNames}
}

// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
	if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
		return nil, false, nil
	}

	// Use intermediates, if provided
	optsCopy, ok := a.verifyOptionsFn()
	// if there are intentionally no verify options, then we cannot authenticate this request
	if !ok {
		return nil, false, nil
	}
	if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
		optsCopy.Intermediates = x509.NewCertPool()
		for _, intermediate := range req.TLS.PeerCertificates[1:] {
			optsCopy.Intermediates.AddCert(intermediate)
		}
	}

	if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
		return nil, false, err
	}
	if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
		return nil, false, err
	}
	return a.auth.AuthenticateRequest(req)
}

func (a *Verifier) verifySubject(subject pkix.Name) error {
	// No CN restrictions
	if len(a.allowedCommonNames.Value()) == 0 {
		return nil
	}
	// Enforce CN restrictions
	for _, allowedCommonName := range a.allowedCommonNames.Value() {
		if allowedCommonName == subject.CommonName {
			return nil
		}
	}
	return fmt.Errorf("x509: subject with cn=%s is not in the allowed list", subject.CommonName)
}

// DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
// and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth)
func DefaultVerifyOptions() x509.VerifyOptions {
	return x509.VerifyOptions{
		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
}

// CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
	if len(chain[0].Subject.CommonName) == 0 {
		return nil, false, nil
	}
	return &authenticator.Response{
		User: &user.DefaultInfo{
			Name:   chain[0].Subject.CommonName,
			Groups: chain[0].Subject.Organization,
		},
	}, true, nil
})
<|endoftext|>


<commit_before>package slackRealtime

import (
	"fmt"
	"log"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/FogCreek/victor/pkg/chat"
	"github.com/nlopes/slack"
)

// The Slack Websocket's registered adapter name for the victor framework.
const AdapterName = "slackRealtime"

// Prefix for the user's ID which is used when reading/writing from the bot's store
const userInfoPrefix = AdapterName + "."

const userIDRegexpString = "\\b<?@?(U[^\\s\\|]+)(?:(?:|\\S+)?>)"

// Match "<@Userid>" and "<@UserID|fullname>"
var userIDRegexp = regexp.MustCompile(userIDRegexpString)

// Match "johndoe", "@johndoe",
// not needed?
// var userIDAndNameRegexp = regexp.MustCompile("\\A@?(\\w+)|" + userIDRegexpString)

// channelGroupInfo is used instead of the slack library's Channel struct since we
// are trying to consider channels and groups to be roughly the same while it
// considers them seperate and provides no way to consolidate them on its own.
//
// This also allows us to throw our information that we don't care about (members, etc.).
type channelGroupInfo struct {
	Name      string
	ID        string
	IsDM      bool
	UserID    string
	IsChannel bool
	// UserID is only stored for IM/DM's so we can then send a user a DM as a
	// response if needed
}

// init registers SlackAdapter to the victor chat framework.
func init() {
	chat.Register(AdapterName, func(r chat.Robot) chat.Adapter {
		config, configSet := r.AdapterConfig()
		if !configSet {
			log.Println("A configuration struct implementing the SlackConfig interface must be set.")
			os.Exit(1)
		}
		sConfig, ok := config.(Config)
		if !ok {
			log.Println("The bot's config must implement the SlackConfig interface.")
			os.Exit(1)
		}
		return &SlackAdapter{
			robot:           r,
			chReceiver:      make(chan slack.SlackEvent),
			token:           sConfig.Token(),
			channelInfo:     make(map[string]channelGroupInfo),
			directMessageID: make(map[string]string),
			userInfo:        make(map[string]slack.User),
		}
	})
}

// Config provides the slack adapter with the necessary
// information to open a websocket connection with the slack Real time API.
type Config interface {
	Token() string
}

// Config implements the SlackRealtimeConfig interface to provide a slack
// adapter with the information it needs to authenticate with slack.
type configImpl struct {
	token string
}

// NewConfig returns a new slack configuration instance using the given token.
func NewConfig(token string) configImpl {
	return configImpl{token: token}
}

// Token returns the slack token.
func (c configImpl) Token() string {
	return c.token
}

// SlackAdapter holds all information needed by the adapter to send/receive messages.
type SlackAdapter struct {
	robot           chat.Robot
	token           string
	instance        *slack.Slack
	wsAPI           *slack.SlackWS
	chReceiver      chan slack.SlackEvent
	channelInfo     map[string]channelGroupInfo
	directMessageID map[string]string
	userInfo        map[string]slack.User
	domain          string
}

// GetUser will parse the given user ID string and then return the user's
// information as provided by the slack API. This will first try to get the
// user's information from a local cache and then will perform a slack API
// call if the user's information is not cached. Returns nil if the user does
// not exist or if an error occurrs during the slack API call.
func (adapter *SlackAdapter) GetUser(userIDStr string) chat.User {
	if !adapter.IsPotentialUser(userIDStr) {
		return nil
	}
	userID := adapter.NormalizeUserID(userIDStr)
	userObj, err := adapter.getUserFromSlack(userID)
	if err != nil {
		return nil
	}
	return &chat.BaseUser{
		UserID:    userObj.Id,
		UserName:  userObj.Name,
		UserEmail: userObj.Profile.Email,
		UserIsBot: userObj.IsBot,
	}
}

// IsPotentialUser checks if a given string is potentially referring to a slack
// user. Strings given to this function should be trimmed of leading whitespace
// as it does not account for that (it is meant to be used with the fields
// method on the frameworks calls to handlers which are trimmed).
func (adapter *SlackAdapter) IsPotentialUser(userString string) bool {
	return userIDRegexp.MatchString(userString)
}

// normalizeUserID returns a user's ID without the extra formatting that slack
// might add. This will return "U01234567" for inputs: "U01234567",
// "@U01234567", "<@U01234567>", and "<@U01234567|name>"
func (adapter *SlackAdapter) NormalizeUserID(userID string) string {
	userIDArr := userIDRegexp.FindAllStringSubmatch(userID, 1)
	if len(userIDArr) == 0 {
		return userID
	}
	return userIDArr[0][1]
}

// Run starts the adapter and begins to listen for new messages to send/receive.
// At the moment this will crash the program and print the error messages to a
// log if the connection fails.
func (adapter *SlackAdapter) Run() error {
	adapter.instance = slack.New(adapter.token)
	adapter.instance.SetDebug(false)
	// TODO need to look up what these values actually mean...
	var err error
	adapter.wsAPI, err = adapter.instance.StartRTM("", "http://example.com")
	// TODO remove fatal crash or recover from it elsewhere
	if err != nil {
		return err
	}
	adapter.initAdapterInfo()
	// sets up the monitoring code for sending/receiving messages from slack
	go adapter.wsAPI.HandleIncomingEvents(adapter.chReceiver)
	go adapter.wsAPI.Keepalive(20 * time.Second)
	go adapter.monitorEvents()
	return nil
}

func (adapter *SlackAdapter) initAdapterInfo() {
	info := adapter.instance.GetInfo()
	adapter.domain = info.Team.Domain
	for _, channel := range info.Channels {
		if !channel.IsMember {
			continue
		}
		adapter.channelInfo[channel.Id] = channelGroupInfo{
			ID:        channel.Id,
			Name:      channel.Name,
			IsChannel: true,
		}
	}
	for _, group := range info.Groups {
		adapter.channelInfo[group.Id] = channelGroupInfo{
			ID:   group.Id,
			Name: group.Name,
		}
	}
	for _, im := range info.IMs {
		adapter.channelInfo[im.Id] = channelGroupInfo{
			ID:     im.Id,
			Name:   fmt.Sprintf("DM %s", im.Id),
			IsDM:   true,
			UserID: im.UserId,
		}
		adapter.directMessageID[im.UserId] = im.Id
	}
	for _, user := range info.Users {
		adapter.userInfo[user.Id] = user
	}
}

// Stop stops the adapter.
// TODO implement
func (adapter *SlackAdapter) Stop() {
}

// ID returns a unique ID for this adapter. At the moment this just returns
// the slack instance token but could be modified to return a uuid using a
// package such as https://godoc.org/code.google.com/p/go-uuid/uuid
func (adapter *SlackAdapter) ID() string {
	return adapter.token
}

func (adapter *SlackAdapter) getUserFromSlack(userID string) (*slack.User, error) {
	// try to get the stored user info
	user, exists := adapter.userInfo[userID]
	// if it hasn't been stored then perform a slack API call to get it and
	// store it
	if !exists {
		user, err := adapter.instance.GetUserInfo(userID)
		if err != nil {
			log.Println(err.Error())
			return nil, err
		}
		// try to encode it as a json string for storage
		adapter.userInfo[user.Id] = *user
		return user, nil
	}

	return &user, nil
}

func (adapter *SlackAdapter) handleMessage(event *slack.MessageEvent) {
	if event.SubType == "message_changed" {
		return
	}
	user, _ := adapter.getUserFromSlack(event.UserId)
	channel, exists := adapter.channelInfo[event.ChannelId]
	if !exists {
		log.Printf("Unrecognized channel with ID %s", event.Id)
		channel = channelGroupInfo{
			Name: "Unrecognized",
			ID:   event.ChannelId,
		}
	}
	// TODO use error
	if user != nil {
		// ignore any messages that are sent by any bot
		if user.IsBot {
			return
		}
		messageText := adapter.unescapeMessage(event.Text)
		var archiveLink string
		if !channel.IsDM {
			archiveLink = adapter.getArchiveLink(channel.Name, event.Timestamp)
		} else {
			archiveLink = "No archive link for Direct Messages"
		}
		msg := chat.BaseMessage{
			MsgUser: &chat.BaseUser{
				UserID:    user.Id,
				UserName:  user.Name,
				UserEmail: user.Profile.Email,
			},
			MsgText:        messageText,
			MsgChannelID:   channel.ID,
			MsgChannelName: channel.Name,
			MsgIsDirect:    channel.IsDM,
			MsgTimestamp:   strings.SplitN(event.Timestamp, ".", 2)[0],
			MsgArchiveLink: archiveLink,
		}
		adapter.robot.Receive(&msg)
	}
}

const archiveURLFormat = "http://%s.slack.com/archives/%s/p%s"

func (adapter *SlackAdapter) getArchiveLink(channelName, timestamp string) string {
	return fmt.Sprintf(archiveURLFormat, adapter.domain, channelName, strings.Replace(timestamp, ".", "", 1))
}

// Replace all instances of the bot's encoded name with it's actual name.
//
// TODO might want to handle unescaping emails and urls here
func (adapter *SlackAdapter) unescapeMessage(msg string) string {
	userID := getEncodedUserID(adapter.instance.GetInfo().User.Id)
	if strings.HasPrefix(msg, userID) {
		return strings.Replace(msg, userID, adapter.robot.Name(), 1)
	}
	return msg
}

// Returns the encoded string version of a user's slack ID.
func getEncodedUserID(userID string) string {
	return fmt.Sprintf("<@%s>", userID)
}

// monitorEvents handles incoming events and filters them to only worry about
// incoming messages.
func (adapter *SlackAdapter) monitorEvents() {
	for {
		msg := <-adapter.chReceiver
		switch msg.Data.(type) {
		case *slack.MessageEvent:
			go adapter.handleMessage(msg.Data.(*slack.MessageEvent))
		case *slack.ChannelJoinedEvent:
			go adapter.joinedChannel(msg.Data.(*slack.ChannelJoinedEvent).Channel, true)
		case *slack.GroupJoinedEvent:
			go adapter.joinedChannel(msg.Data.(*slack.GroupJoinedEvent).Channel, false)
		case *slack.IMCreatedEvent:
			// could also use im open? https://api.slack.com/events/im_created
			go adapter.joinedIM(msg.Data.(*slack.IMCreatedEvent))
		case *slack.ChannelLeftEvent:
			go adapter.leftChannel(msg.Data.(*slack.ChannelLeftEvent).ChannelId)
		case *slack.GroupLeftEvent:
			go adapter.leftChannel(msg.Data.(*slack.GroupLeftEvent).ChannelId)
		case *slack.IMCloseEvent:
			go adapter.leftIM(msg.Data.(*slack.IMCloseEvent))
		case *slack.TeamDomainChangeEvent:
			go adapter.domainChanged(msg.Data.(*slack.TeamDomainChangeEvent))
		case *slack.UserChangeEvent:
			go adapter.userChanged(msg.Data.(*slack.UserChangeEvent).User)
		case *slack.TeamJoinEvent:
			// Need to dereference the user object due to inconsistencies in
			// the go slack api.
			go adapter.userChanged(*(msg.Data.(*slack.TeamJoinEvent).User))
		}
	}
}

func (adapter *SlackAdapter) userChanged(user slack.User) {
	if user.IsBot {
		return
	}
	adapter.userInfo[user.Id] = user
}

func (adapter *SlackAdapter) domainChanged(event *slack.TeamDomainChangeEvent) {
	adapter.domain = event.Domain
}

func (adapter *SlackAdapter) joinedChannel(channel slack.Channel, isChannel bool) {
	// log.Printf("Loaded info for channel/group \"%s\"", channel.Name)
	adapter.channelInfo[channel.Id] = channelGroupInfo{
		Name:      channel.Name,
		ID:        channel.Id,
		IsChannel: isChannel,
	}
}

func (adapter *SlackAdapter) joinedIM(event *slack.IMCreatedEvent) {
	// log.Printf("Loaded info for IM \"%s\"", event.Channel.Id)
	adapter.channelInfo[event.Channel.Id] = channelGroupInfo{
		Name:   event.Channel.Name,
		ID:     event.Channel.Id,
		IsDM:   true,
		UserID: event.UserId,
	}
	adapter.directMessageID[event.UserId] = event.Channel.Id
}

func (adapter *SlackAdapter) leftIM(event *slack.IMCloseEvent) {
	adapter.leftChannel(event.ChannelId)
	delete(adapter.directMessageID, event.UserId)
}

func (adapter *SlackAdapter) leftChannel(channelID string) {
	// log.Printf("Forgetting channel/group with ID %s", channelID)
	delete(adapter.channelInfo, channelID)
}

// Send sends a message to the given slack channel.
func (adapter *SlackAdapter) Send(channelID, msg string) {
	msgObj := adapter.wsAPI.NewOutgoingMessage(msg, channelID)
	adapter.wsAPI.SendMessage(msgObj)
}

// SendDirectMessage sends the given message to the given user in a direct
// (private) message.
func (adapter *SlackAdapter) SendDirectMessage(userID, msg string) {
	channelID, err := adapter.getDirectMessageID(userID)
	if err != nil {
		log.Printf("Error getting direct message channel ID for user \"%s\": %s", userID, err.Error())
		return
	}
	adapter.Send(channelID, msg)
}

func (adapter *SlackAdapter) getDirectMessageID(userID string) (string, error) {
	// need to figure out if the first two bool return values are important
	// https://github.com/nlopes/slack/blob/master/dm.go#L58
	channel, exists := adapter.channelInfo[userID]
	if !exists {
		_, _, channelID, err := adapter.instance.OpenIMChannel(userID)
		return channelID, err
	}
	return channel.ID, nil
}
<commit_msg>Fixed error on message deletion/edit<commit_after>package slackRealtime

import (
	"fmt"
	"log"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/FogCreek/victor/pkg/chat"
	"github.com/nlopes/slack"
)

// The Slack Websocket's registered adapter name for the victor framework.
const AdapterName = "slackRealtime"

// Prefix for the user's ID which is used when reading/writing from the bot's store
const userInfoPrefix = AdapterName + "."

const userIDRegexpString = "\\b<?@?(U[^\\s\\|]+)(?:(?:|\\S+)?>)"

// Match "<@Userid>" and "<@UserID|fullname>"
var userIDRegexp = regexp.MustCompile(userIDRegexpString)

// Match "johndoe", "@johndoe",
// not needed?
// var userIDAndNameRegexp = regexp.MustCompile("\\A@?(\\w+)|" + userIDRegexpString)

// channelGroupInfo is used instead of the slack library's Channel struct since we
// are trying to consider channels and groups to be roughly the same while it
// considers them seperate and provides no way to consolidate them on its own.
//
// This also allows us to throw our information that we don't care about (members, etc.).
type channelGroupInfo struct {
	Name      string
	ID        string
	IsDM      bool
	UserID    string
	IsChannel bool
	// UserID is only stored for IM/DM's so we can then send a user a DM as a
	// response if needed
}

// init registers SlackAdapter to the victor chat framework.
func init() {
	chat.Register(AdapterName, func(r chat.Robot) chat.Adapter {
		config, configSet := r.AdapterConfig()
		if !configSet {
			log.Println("A configuration struct implementing the SlackConfig interface must be set.")
			os.Exit(1)
		}
		sConfig, ok := config.(Config)
		if !ok {
			log.Println("The bot's config must implement the SlackConfig interface.")
			os.Exit(1)
		}
		return &SlackAdapter{
			robot:           r,
			chReceiver:      make(chan slack.SlackEvent),
			token:           sConfig.Token(),
			channelInfo:     make(map[string]channelGroupInfo),
			directMessageID: make(map[string]string),
			userInfo:        make(map[string]slack.User),
		}
	})
}

// Config provides the slack adapter with the necessary
// information to open a websocket connection with the slack Real time API.
type Config interface {
	Token() string
}

// Config implements the SlackRealtimeConfig interface to provide a slack
// adapter with the information it needs to authenticate with slack.
type configImpl struct {
	token string
}

// NewConfig returns a new slack configuration instance using the given token.
func NewConfig(token string) configImpl {
	return configImpl{token: token}
}

// Token returns the slack token.
func (c configImpl) Token() string {
	return c.token
}

// SlackAdapter holds all information needed by the adapter to send/receive messages.
type SlackAdapter struct {
	robot           chat.Robot
	token           string
	instance        *slack.Slack
	wsAPI           *slack.SlackWS
	chReceiver      chan slack.SlackEvent
	channelInfo     map[string]channelGroupInfo
	directMessageID map[string]string
	userInfo        map[string]slack.User
	domain          string
}

// GetUser will parse the given user ID string and then return the user's
// information as provided by the slack API. This will first try to get the
// user's information from a local cache and then will perform a slack API
// call if the user's information is not cached. Returns nil if the user does
// not exist or if an error occurrs during the slack API call.
func (adapter *SlackAdapter) GetUser(userIDStr string) chat.User {
	if !adapter.IsPotentialUser(userIDStr) {
		return nil
	}
	userID := adapter.NormalizeUserID(userIDStr)
	userObj, err := adapter.getUserFromSlack(userID)
	if err != nil {
		return nil
	}
	return &chat.BaseUser{
		UserID:    userObj.Id,
		UserName:  userObj.Name,
		UserEmail: userObj.Profile.Email,
		UserIsBot: userObj.IsBot,
	}
}

// IsPotentialUser checks if a given string is potentially referring to a slack
// user. Strings given to this function should be trimmed of leading whitespace
// as it does not account for that (it is meant to be used with the fields
// method on the frameworks calls to handlers which are trimmed).
func (adapter *SlackAdapter) IsPotentialUser(userString string) bool {
	return userIDRegexp.MatchString(userString)
}

// normalizeUserID returns a user's ID without the extra formatting that slack
// might add. This will return "U01234567" for inputs: "U01234567",
// "@U01234567", "<@U01234567>", and "<@U01234567|name>"
func (adapter *SlackAdapter) NormalizeUserID(userID string) string {
	userIDArr := userIDRegexp.FindAllStringSubmatch(userID, 1)
	if len(userIDArr) == 0 {
		return userID
	}
	return userIDArr[0][1]
}

// Run starts the adapter and begins to listen for new messages to send/receive.
// At the moment this will crash the program and print the error messages to a
// log if the connection fails.
func (adapter *SlackAdapter) Run() error {
	adapter.instance = slack.New(adapter.token)
	adapter.instance.SetDebug(false)
	// TODO need to look up what these values actually mean...
	var err error
	adapter.wsAPI, err = adapter.instance.StartRTM("", "http://example.com")
	// TODO remove fatal crash or recover from it elsewhere
	if err != nil {
		return err
	}
	adapter.initAdapterInfo()
	// sets up the monitoring code for sending/receiving messages from slack
	go adapter.wsAPI.HandleIncomingEvents(adapter.chReceiver)
	go adapter.wsAPI.Keepalive(20 * time.Second)
	go adapter.monitorEvents()
	return nil
}

func (adapter *SlackAdapter) initAdapterInfo() {
	info := adapter.instance.GetInfo()
	adapter.domain = info.Team.Domain
	for _, channel := range info.Channels {
		if !channel.IsMember {
			continue
		}
		adapter.channelInfo[channel.Id] = channelGroupInfo{
			ID:        channel.Id,
			Name:      channel.Name,
			IsChannel: true,
		}
	}
	for _, group := range info.Groups {
		adapter.channelInfo[group.Id] = channelGroupInfo{
			ID:   group.Id,
			Name: group.Name,
		}
	}
	for _, im := range info.IMs {
		adapter.channelInfo[im.Id] = channelGroupInfo{
			ID:     im.Id,
			Name:   fmt.Sprintf("DM %s", im.Id),
			IsDM:   true,
			UserID: im.UserId,
		}
		adapter.directMessageID[im.UserId] = im.Id
	}
	for _, user := range info.Users {
		adapter.userInfo[user.Id] = user
	}
}

// Stop stops the adapter.
// TODO implement
func (adapter *SlackAdapter) Stop() {
}

// ID returns a unique ID for this adapter. At the moment this just returns
// the slack instance token but could be modified to return a uuid using a
// package such as https://godoc.org/code.google.com/p/go-uuid/uuid
func (adapter *SlackAdapter) ID() string {
	return adapter.token
}

func (adapter *SlackAdapter) getUserFromSlack(userID string) (*slack.User, error) {
	// try to get the stored user info
	user, exists := adapter.userInfo[userID]
	// if it hasn't been stored then perform a slack API call to get it and
	// store it
	if !exists {
		user, err := adapter.instance.GetUserInfo(userID)
		if err != nil {
			log.Println(err.Error())
			return nil, err
		}
		// try to encode it as a json string for storage
		adapter.userInfo[user.Id] = *user
		return user, nil
	}

	return &user, nil
}

func (adapter *SlackAdapter) handleMessage(event *slack.MessageEvent) {
	if len(event.SubType) > 0 {
		return
	}
	user, _ := adapter.getUserFromSlack(event.UserId)
	channel, exists := adapter.channelInfo[event.ChannelId]
	if !exists {
		log.Printf("Unrecognized channel with ID %s", event.Id)
		channel = channelGroupInfo{
			Name: "Unrecognized",
			ID:   event.ChannelId,
		}
	}
	// TODO use error
	if user != nil {
		// ignore any messages that are sent by any bot
		if user.IsBot {
			return
		}
		messageText := adapter.unescapeMessage(event.Text)
		var archiveLink string
		if !channel.IsDM {
			archiveLink = adapter.getArchiveLink(channel.Name, event.Timestamp)
		} else {
			archiveLink = "No archive link for Direct Messages"
		}
		msg := chat.BaseMessage{
			MsgUser: &chat.BaseUser{
				UserID:    user.Id,
				UserName:  user.Name,
				UserEmail: user.Profile.Email,
			},
			MsgText:        messageText,
			MsgChannelID:   channel.ID,
			MsgChannelName: channel.Name,
			MsgIsDirect:    channel.IsDM,
			MsgTimestamp:   strings.SplitN(event.Timestamp, ".", 2)[0],
			MsgArchiveLink: archiveLink,
		}
		adapter.robot.Receive(&msg)
	}
}

const archiveURLFormat = "http://%s.slack.com/archives/%s/p%s"

func (adapter *SlackAdapter) getArchiveLink(channelName, timestamp string) string {
	return fmt.Sprintf(archiveURLFormat, adapter.domain, channelName, strings.Replace(timestamp, ".", "", 1))
}

// Replace all instances of the bot's encoded name with it's actual name.
//
// TODO might want to handle unescaping emails and urls here
func (adapter *SlackAdapter) unescapeMessage(msg string) string {
	userID := getEncodedUserID(adapter.instance.GetInfo().User.Id)
	if strings.HasPrefix(msg, userID) {
		return strings.Replace(msg, userID, adapter.robot.Name(), 1)
	}
	return msg
}

// Returns the encoded string version of a user's slack ID.
func getEncodedUserID(userID string) string {
	return fmt.Sprintf("<@%s>", userID)
}

// monitorEvents handles incoming events and filters them to only worry about
// incoming messages.
func (adapter *SlackAdapter) monitorEvents() {
	for {
		msg := <-adapter.chReceiver
		switch msg.Data.(type) {
		case *slack.MessageEvent:
			go adapter.handleMessage(msg.Data.(*slack.MessageEvent))
		case *slack.ChannelJoinedEvent:
			go adapter.joinedChannel(msg.Data.(*slack.ChannelJoinedEvent).Channel, true)
		case *slack.GroupJoinedEvent:
			go adapter.joinedChannel(msg.Data.(*slack.GroupJoinedEvent).Channel, false)
		case *slack.IMCreatedEvent:
			// could also use im open? https://api.slack.com/events/im_created
			go adapter.joinedIM(msg.Data.(*slack.IMCreatedEvent))
		case *slack.ChannelLeftEvent:
			go adapter.leftChannel(msg.Data.(*slack.ChannelLeftEvent).ChannelId)
		case *slack.GroupLeftEvent:
			go adapter.leftChannel(msg.Data.(*slack.GroupLeftEvent).ChannelId)
		case *slack.IMCloseEvent:
			go adapter.leftIM(msg.Data.(*slack.IMCloseEvent))
		case *slack.TeamDomainChangeEvent:
			go adapter.domainChanged(msg.Data.(*slack.TeamDomainChangeEvent))
		case *slack.UserChangeEvent:
			go adapter.userChanged(msg.Data.(*slack.UserChangeEvent).User)
		case *slack.TeamJoinEvent:
			// Need to dereference the user object due to inconsistencies in
			// the go slack api.
			go adapter.userChanged(*(msg.Data.(*slack.TeamJoinEvent).User))
		}
	}
}

func (adapter *SlackAdapter) userChanged(user slack.User) {
	if user.IsBot {
		return
	}
	adapter.userInfo[user.Id] = user
}

func (adapter *SlackAdapter) domainChanged(event *slack.TeamDomainChangeEvent) {
	adapter.domain = event.Domain
}

func (adapter *SlackAdapter) joinedChannel(channel slack.Channel, isChannel bool) {
	// log.Printf("Loaded info for channel/group \"%s\"", channel.Name)
	adapter.channelInfo[channel.Id] = channelGroupInfo{
		Name:      channel.Name,
		ID:        channel.Id,
		IsChannel: isChannel,
	}
}

func (adapter *SlackAdapter) joinedIM(event *slack.IMCreatedEvent) {
	// log.Printf("Loaded info for IM \"%s\"", event.Channel.Id)
	adapter.channelInfo[event.Channel.Id] = channelGroupInfo{
		Name:   event.Channel.Name,
		ID:     event.Channel.Id,
		IsDM:   true,
		UserID: event.UserId,
	}
	adapter.directMessageID[event.UserId] = event.Channel.Id
}

func (adapter *SlackAdapter) leftIM(event *slack.IMCloseEvent) {
	adapter.leftChannel(event.ChannelId)
	delete(adapter.directMessageID, event.UserId)
}

func (adapter *SlackAdapter) leftChannel(channelID string) {
	// log.Printf("Forgetting channel/group with ID %s", channelID)
	delete(adapter.channelInfo, channelID)
}

// Send sends a message to the given slack channel.
func (adapter *SlackAdapter) Send(channelID, msg string) {
	msgObj := adapter.wsAPI.NewOutgoingMessage(msg, channelID)
	adapter.wsAPI.SendMessage(msgObj)
}

// SendDirectMessage sends the given message to the given user in a direct
// (private) message.
func (adapter *SlackAdapter) SendDirectMessage(userID, msg string) {
	channelID, err := adapter.getDirectMessageID(userID)
	if err != nil {
		log.Printf("Error getting direct message channel ID for user \"%s\": %s", userID, err.Error())
		return
	}
	adapter.Send(channelID, msg)
}

func (adapter *SlackAdapter) getDirectMessageID(userID string) (string, error) {
	// need to figure out if the first two bool return values are important
	// https://github.com/nlopes/slack/blob/master/dm.go#L58
	channel, exists := adapter.channelInfo[userID]
	if !exists {
		_, _, channelID, err := adapter.instance.OpenIMChannel(userID)
		return channelID, err
	}
	return channel.ID, nil
}
<|endoftext|>


<commit_before>package comments

import (
	"fmt"
	"strconv"

	"github.com/NyaaPantsu/nyaa/models"
	"github.com/NyaaPantsu/nyaa/models/notifications"
	"github.com/NyaaPantsu/nyaa/utils/publicSettings"
)

func NewCommentEvent(comment *models.Comment, torrent *models.Torrent) {
	url := "/view/" + strconv.FormatUint(uint64(torrent.ID), 10)
	if torrent.UploaderID > 0 {
		torrent.Uploader.ParseSettings()
		if torrent.Uploader.Settings.Get("new_comment") {
			T, _, _ := publicSettings.TfuncAndLanguageWithFallback(torrent.Uploader.Language, torrent.Uploader.Language) // We need to send the notification to every user in their language
			notifications.NotifyUser(torrent.Uploader, comment.Identifier(), fmt.Sprintf(T("new_comment_on_torrent"), torrent.Name), url, torrent.Uploader.Settings.Get("new_comment_email"))
		}
	}
}
<commit_msg>Fix notifications<commit_after>package comments

import (
	"fmt"
	"strconv"

	"github.com/NyaaPantsu/nyaa/models"
	"github.com/NyaaPantsu/nyaa/models/notifications"
	"github.com/NyaaPantsu/nyaa/utils/publicSettings"
)

// When a new comment is added this is called
func NewCommentEvent(comment *models.Comment, torrent *models.Torrent) {
	comment.Torrent = torrent
	url := "/view/" + strconv.FormatUint(uint64(torrent.ID), 10)
	if torrent.UploaderID > 0 {
		torrent.Uploader.ParseSettings()
		if torrent.Uploader.Settings.Get("new_comment") {
			T, _, _ := publicSettings.TfuncAndLanguageWithFallback(torrent.Uploader.Language, torrent.Uploader.Language) // We need to send the notification to every user in their language
			notifications.NotifyUser(torrent.Uploader, comment.Identifier(), fmt.Sprintf(T("new_comment_on_torrent"), torrent.Name), url, torrent.Uploader.Settings.Get("new_comment_email"))
		}
	}
}
<|endoftext|>


<commit_before>package core

import (
	"testing"
)

func TestChangeCopySlim(t *testing.T) {
	// Create a sample change.
	change := &Change{
		Path: "test",
		Old:  nil,
		New:  testDirectory2Entry,
	}

	// Create a slim copy.
	slim := change.copySlim()

	// Check validity.
	if err := slim.EnsureValid(); err != nil {
		t.Fatal("slim copy of change is invalid:", err)
	}

	// Check path.
	if slim.Path != "test" {
		t.Error("slim copy of change has differing path")
	}

	// Check old entry.
	if !slim.Old.Equal(nil) {
		t.Error("slim copy of change has incorrect old entry")
	}

	// Check new entry.
	if !slim.New.Equal(testEmptyDirectory) {
		t.Error("slim copy of change has incorrect new entry")
	}
}

func TestChangeNilInvalid(t *testing.T) {
	var change *Change
	if change.EnsureValid() == nil {
		t.Error("nil change considered valid")
	}
}

func TestChangeValid(t *testing.T) {
	change := &Change{New: testSymlinkEntry}
	if err := change.EnsureValid(); err != nil {
		t.Error("valid change considered invalid:", err)
	}
}

// TestIsRootDeletion tests that Change.IsRootDeletion behaves as expected.
func TestIsRootDeletion(t *testing.T) {
	// Set up test cases.
	testCases := []struct {
		change               *Change
		expectIsRootDeletion bool
	}{
		{&Change{New: testDirectory1Entry}, false},
		{&Change{Old: testFile1Entry, New: testDirectory1Entry}, false},
		{&Change{}, false},
		{&Change{Old: testFile1Entry, New: testFile1Entry}, false},
		{&Change{Old: testFile1Entry}, true},
		{&Change{Old: testDirectory1Entry}, true},
	}

	// Process test cases.
	for _, testCase := range testCases {
		isRootDeletion := testCase.change.IsRootDeletion()
		if isRootDeletion && !testCase.expectIsRootDeletion {
			t.Error("test case incorrectly classified as root deletion")
		} else if !isRootDeletion && testCase.expectIsRootDeletion {
			t.Error("test case not correctly classified as root deletion")
		}
	}
}

// TestIsRootTypeChange tests that Change.IsRootTypeChange behaves as expected.
func TestIsRootTypeChange(t *testing.T) {
	// Set up test cases.
	testCases := []struct {
		change                 *Change
		expectIsRootTypeChange bool
	}{
		{&Change{}, false},
		{&Change{Old: testFile1Entry}, false},
		{&Change{Old: testDirectory1Entry}, false},
		{&Change{Old: testFile1Entry, New: testFile1Entry}, false},
		{&Change{Old: testDirectory1Entry, New: testDirectory1Entry}, false},
		{&Change{Old: testFile1Entry, New: testDirectory1Entry}, true},
		{&Change{Old: testDirectory1Entry, New: testFile1Entry}, true},
	}

	// Process test cases.
	for _, testCase := range testCases {
		isRootTypeChange := testCase.change.IsRootTypeChange()
		if isRootTypeChange && !testCase.expectIsRootTypeChange {
			t.Error("test case incorrectly classified as root type change")
		} else if !isRootTypeChange && testCase.expectIsRootTypeChange {
			t.Error("test case not correctly classified as root type change")
		}
	}
}
<commit_msg>Expanded TestIsRootTypeChange test cases.<commit_after>package core

import (
	"testing"
)

func TestChangeCopySlim(t *testing.T) {
	// Create a sample change.
	change := &Change{
		Path: "test",
		Old:  nil,
		New:  testDirectory2Entry,
	}

	// Create a slim copy.
	slim := change.copySlim()

	// Check validity.
	if err := slim.EnsureValid(); err != nil {
		t.Fatal("slim copy of change is invalid:", err)
	}

	// Check path.
	if slim.Path != "test" {
		t.Error("slim copy of change has differing path")
	}

	// Check old entry.
	if !slim.Old.Equal(nil) {
		t.Error("slim copy of change has incorrect old entry")
	}

	// Check new entry.
	if !slim.New.Equal(testEmptyDirectory) {
		t.Error("slim copy of change has incorrect new entry")
	}
}

func TestChangeNilInvalid(t *testing.T) {
	var change *Change
	if change.EnsureValid() == nil {
		t.Error("nil change considered valid")
	}
}

func TestChangeValid(t *testing.T) {
	change := &Change{New: testSymlinkEntry}
	if err := change.EnsureValid(); err != nil {
		t.Error("valid change considered invalid:", err)
	}
}

// TestIsRootDeletion tests that Change.IsRootDeletion behaves as expected.
func TestIsRootDeletion(t *testing.T) {
	// Set up test cases.
	testCases := []struct {
		change               *Change
		expectIsRootDeletion bool
	}{
		{&Change{New: testDirectory1Entry}, false},
		{&Change{Old: testFile1Entry, New: testDirectory1Entry}, false},
		{&Change{}, false},
		{&Change{Old: testFile1Entry, New: testFile1Entry}, false},
		{&Change{Old: testFile1Entry}, true},
		{&Change{Old: testDirectory1Entry}, true},
	}

	// Process test cases.
	for _, testCase := range testCases {
		isRootDeletion := testCase.change.IsRootDeletion()
		if isRootDeletion && !testCase.expectIsRootDeletion {
			t.Error("test case incorrectly classified as root deletion")
		} else if !isRootDeletion && testCase.expectIsRootDeletion {
			t.Error("test case not correctly classified as root deletion")
		}
	}
}

// TestIsRootTypeChange tests that Change.IsRootTypeChange behaves as expected.
func TestIsRootTypeChange(t *testing.T) {
	// Set up test cases.
	testCases := []struct {
		change                 *Change
		expectIsRootTypeChange bool
	}{
		{&Change{}, false},
		{&Change{Old: testFile1Entry}, false},
		{&Change{Old: testDirectory1Entry}, false},
		{&Change{Old: testFile1Entry, New: testFile1Entry}, false},
		{&Change{Old: testDirectory1Entry, New: testDirectory1Entry}, false},
		{&Change{Old: testFile1Entry, New: testFile2Entry}, false},
		{&Change{Old: testDirectory1Entry, New: testDirectory2Entry}, false},
		{&Change{Old: testFile1Entry, New: testDirectory1Entry}, true},
		{&Change{Old: testDirectory1Entry, New: testFile1Entry}, true},
	}

	// Process test cases.
	for _, testCase := range testCases {
		isRootTypeChange := testCase.change.IsRootTypeChange()
		if isRootTypeChange && !testCase.expectIsRootTypeChange {
			t.Error("test case incorrectly classified as root type change")
		} else if !isRootTypeChange && testCase.expectIsRootTypeChange {
			t.Error("test case not correctly classified as root type change")
		}
	}
}
<|endoftext|>