<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus"
)

func TestExporterError(t *testing.T) {
	var delay time.Duration

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		<-time.After(delay)
		fmt.Fprintf(w, "malformed JSON content")
	}))
	defer ts.Close()

	delay = 10 * time.Millisecond
	e := NewExporter(ts.URL, 5*time.Millisecond)
	e.scraping = true
	e.scrapeAll()
	if e.scraping {
		t.Fatalf("scraping status not reset on timeout")
	}

	delay = 0
	e = NewExporter(ts.URL, 0)
	e.scraping = true
	e.scrapeAll()
	if e.scraping {
		t.Fatalf("scraping not reset on decoding error")
	}
}

func TestExporterFastCollect(t *testing.T) {
	var (
		block = make(chan bool)
		reqs  = make(chan bool, 10)
	)
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		reqs <- true
		<-block
	}))
	defer ts.Close()

	// check if calling collect multiple before a scrape can finish accumulates scrapes
	e := NewExporter(ts.URL, 0)
	cch := make(chan prometheus.Metric, 100)

	e.Collect(cch)
	e.Collect(cch)
	e.Collect(cch)
	<-reqs
	close(block)

	if len(reqs) != 0 {
		t.Fatalf("expected %d executed scrape(s), got %d", 1, len(reqs)+1)
	}
}

func TestCollector(t *testing.T) {
	c := newCollector("test")

	c.gaugeVec("test_gauge", "test_help", nil)
	if v, ok := c.gaugeVecs["test_gauge"]; !ok {
		t.Fatalf("registered gauge vector missing")
	} else {
		v.With(nil).Set(1)
	}

	c.summaryVec("test_summary", "test_help", nil)
	if v, ok := c.summaryVecs["test_summary"]; !ok {
		t.Fatalf("registered summary vector missing")
	} else {
		v.With(nil).Observe(1)
	}

	c.counterVec("test_counter", "test_help", nil)
	if v, ok := c.counterVecs["test_counter"]; !ok {
		t.Fatalf("registered counter vector missing")
	} else {
		v.With(nil).Inc()
	}

	dch := make(chan *prometheus.Desc, 10)
	c.Describe(dch)
	if len(dch) != 3 {
		t.Fatalf("inserted %d metrics but %d were described", 3, len(dch))
	}

	cch := make(chan prometheus.Metric, 10)
	c.Collect(cch)
	if len(cch) != 3 {
		t.Fatalf("inserted %d metrics but %d were collected", 3, len(cch))
	}
}

func TestTime3339(t *testing.T) {
	ts := "2014-01-01T15:26:24.96569404Z"

	var tmp Time3339
	err := json.Unmarshal([]byte("\""+ts+"\""), &tmp)
	if err != nil {
		t.Fatalf("error decoding JSON timestamp: %s", err)
	}

	tmc, err := time.Parse(time.RFC3339Nano, ts)
	if err != nil {
		t.Fatalf("error parsing timestamp: %s", err)
	}
	if !tmc.Equal(time.Time(tmp)) {
		t.Fatalf("decoded time mismatch: got %s, expected %s", tmp, tmc)
	}

	err = json.Unmarshal([]byte("null"), &tmp)
	if err != nil {
		t.Fatalf("error decoding JSON timestamp: %s", err)
	}
	if !time.Time(tmp).Equal(time.Time{}) {
		t.Fatalf("null not parsed to zero time, got %s", tmp)
	}

	err = json.Unmarshal([]byte("\"\""), &tmp)
	if err != nil {
		t.Fatalf("error decoding JSON timestamp: %s", err)
	}
	if !time.Time(tmp).Equal(time.Time{}) {
		t.Fatalf("empty string not parsed to zero time, got %s", tmp)
	}
}
<commit_msg>test fix<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus"
)

func TestExporterError(t *testing.T) {
	var delay time.Duration

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		<-time.After(delay)
		fmt.Fprintf(w, "malformed JSON content")
	}))
	defer ts.Close()

	delay = 10 * time.Millisecond
	e := NewExporter(ts.URL, 5*time.Millisecond)
	e.scraping = true
	e.scrapeAll()
	if e.scraping {
		t.Fatalf("scraping status not reset on timeout")
	}

	delay = 0
	e = NewExporter(ts.URL, 0)
	e.scraping = true
	e.scrapeAll()
	if e.scraping {
		t.Fatalf("scraping not reset on decoding error")
	}
}

func TestExporterFastCollect(t *testing.T) {
	var (
		block = make(chan bool)
		reqs  = make(chan bool, 10)
	)
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		reqs <- true
		<-block
	}))
	defer ts.Close()

	// check if calling collect multiple before a scrape can finish accumulates scrapes
	e := NewExporter(ts.URL, 50*time.Millisecond)
	cch := make(chan prometheus.Metric, 100)

	e.Collect(cch)
	e.Collect(cch)
	<-reqs

	select {
	case <-reqs:
		t.Fatalf("unexpected scrape request came through")
	case <-time.After(100 * time.Millisecond):
	}
	close(block)
}

func TestCollector(t *testing.T) {
	c := newCollector("test")

	c.gaugeVec("test_gauge", "test_help", nil)
	if v, ok := c.gaugeVecs["test_gauge"]; !ok {
		t.Fatalf("registered gauge vector missing")
	} else {
		v.With(nil).Set(1)
	}

	c.summaryVec("test_summary", "test_help", nil)
	if v, ok := c.summaryVecs["test_summary"]; !ok {
		t.Fatalf("registered summary vector missing")
	} else {
		v.With(nil).Observe(1)
	}

	c.counterVec("test_counter", "test_help", nil)
	if v, ok := c.counterVecs["test_counter"]; !ok {
		t.Fatalf("registered counter vector missing")
	} else {
		v.With(nil).Inc()
	}

	dch := make(chan *prometheus.Desc, 10)
	c.Describe(dch)
	if len(dch) != 3 {
		t.Fatalf("inserted %d metrics but %d were described", 3, len(dch))
	}

	cch := make(chan prometheus.Metric, 10)
	c.Collect(cch)
	if len(cch) != 3 {
		t.Fatalf("inserted %d metrics but %d were collected", 3, len(cch))
	}
}

func TestTime3339(t *testing.T) {
	ts := "2014-01-01T15:26:24.96569404Z"

	var tmp Time3339
	err := json.Unmarshal([]byte("\""+ts+"\""), &tmp)
	if err != nil {
		t.Fatalf("error decoding JSON timestamp: %s", err)
	}

	tmc, err := time.Parse(time.RFC3339Nano, ts)
	if err != nil {
		t.Fatalf("error parsing timestamp: %s", err)
	}
	if !tmc.Equal(time.Time(tmp)) {
		t.Fatalf("decoded time mismatch: got %s, expected %s", tmp, tmc)
	}

	err = json.Unmarshal([]byte("null"), &tmp)
	if err != nil {
		t.Fatalf("error decoding JSON timestamp: %s", err)
	}
	if !time.Time(tmp).Equal(time.Time{}) {
		t.Fatalf("null not parsed to zero time, got %s", tmp)
	}

	err = json.Unmarshal([]byte("\"\""), &tmp)
	if err != nil {
		t.Fatalf("error decoding JSON timestamp: %s", err)
	}
	if !time.Time(tmp).Equal(time.Time{}) {
		t.Fatalf("empty string not parsed to zero time, got %s", tmp)
	}
}
<|endoftext|>


<commit_before>// All files containing a main method must be in the main package
package main

// fmt contains methods for formatting data in strings and io
// https://golang.org/pkg/fmt/
import "fmt"

// Not that there is no return value or arguments to our main method. We will get to that later.
func main() {
    // A function with a capital letter?!?! What is that about? Yeah, we'll get to that later as well.
    fmt.Println("Hello,", "gopher!") // Arguments will be converted to strings and concatenated together with spaces in between
    fmt.Printf("Goodbye, %s.\n", "gopher") // Does what you would think.
}
// BTW, where are all the semicolons.<commit_msg>Some final tweaks to hello<commit_after>// All files containing a main method must be in the main package
package main

// fmt contains methods for formatting data in strings and io
// https://golang.org/pkg/fmt/
import "fmt"

// Not that there is no return value or arguments to our main method. We will get to that later.
func main() {
    // A function with a capital letter?!?! What is that about? Yeah, we'll get to that later as well.
    fmt.Println("Hello,", "gopher!") // Arguments will be converted to strings and concatenated together with spaces in between
    fmt.Printf("Goodbye, %s.\n", "gopher") // Does what you would think.
    // BTW, where are all the semicolons?
}

// Let's try some things at the command line
// go build
// go install
<|endoftext|>


<commit_before>// +build kvdb_etcd

package etcd

import (
	"strconv"

	"github.com/btcsuite/btcwallet/walletdb"
)

// readWriteBucket stores the bucket id and the buckets transaction.
type readWriteBucket struct {
	// id is used to identify the bucket and is created by
	// hashing the parent id with the bucket key. For each key/value,
	// sub-bucket or the bucket sequence the bucket id is used with the
	// appropriate prefix to prefix the key.
	id []byte

	// tx holds the parent transaction.
	tx *readWriteTx
}

// newReadWriteBucket creates a new rw bucket with the passed transaction
// and bucket id.
func newReadWriteBucket(tx *readWriteTx, key, id []byte) *readWriteBucket {
	return &readWriteBucket{
		id: id,
		tx: tx,
	}
}

// NestedReadBucket retrieves a nested read bucket with the given key.
// Returns nil if the bucket does not exist.
func (b *readWriteBucket) NestedReadBucket(key []byte) walletdb.ReadBucket {
	return b.NestedReadWriteBucket(key)
}

// ForEach invokes the passed function with every key/value pair in
// the bucket. This includes nested buckets, in which case the value
// is nil, but it does not include the key/value pairs within those
// nested buckets.
func (b *readWriteBucket) ForEach(cb func(k, v []byte) error) error {
	prefix := string(b.id)

	// Get the first matching key that is in the bucket.
	kv, err := b.tx.stm.First(prefix)
	if err != nil {
		return err
	}

	for kv != nil {
		key, val := getKeyVal(kv)

		if err := cb(key, val); err != nil {
			return err
		}

		// Step to the next key.
		kv, err = b.tx.stm.Next(prefix, kv.key)
		if err != nil {
			return err
		}
	}

	return nil
}

// Get returns the value for the given key. Returns nil if the key does
// not exist in this bucket.
func (b *readWriteBucket) Get(key []byte) []byte {
	// Return nil if the key is empty.
	if len(key) == 0 {
		return nil
	}

	// Fetch the associated value.
	val, err := b.tx.stm.Get(string(makeValueKey(b.id, key)))
	if err != nil {
		// TODO: we should return the error once the
		// kvdb inteface is extended.
		return nil
	}

	if val == nil {
		return nil
	}

	return val
}

func (b *readWriteBucket) ReadCursor() walletdb.ReadCursor {
	return newReadWriteCursor(b)
}

// NestedReadWriteBucket retrieves a nested bucket with the given key.
// Returns nil if the bucket does not exist.
func (b *readWriteBucket) NestedReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
	if len(key) == 0 {
		return nil
	}

	// Get the bucket id (and return nil if bucket doesn't exist).
	bucketKey := makeBucketKey(b.id, key)
	bucketVal, err := b.tx.stm.Get(string(bucketKey))
	if err != nil {
		// TODO: we should return the error once the
		// kvdb inteface is extended.
		return nil
	}

	if !isValidBucketID(bucketVal) {
		return nil
	}

	// Return the bucket with the fetched bucket id.
	return newReadWriteBucket(b.tx, bucketKey, bucketVal)
}

// assertNoValue checks if the value for the passed key exists.
func (b *readWriteBucket) assertNoValue(key []byte) error {
	val, err := b.tx.stm.Get(string(makeValueKey(b.id, key)))
	if err != nil {
		return err
	}

	if val != nil {
		return walletdb.ErrIncompatibleValue
	}

	return nil
}

// CreateBucket creates and returns a new nested bucket with the given
// key. Returns ErrBucketExists if the bucket already exists,
// ErrBucketNameRequired if the key is empty, or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// implementation.  Other errors are possible depending on the
// implementation.
func (b *readWriteBucket) CreateBucket(key []byte) (
	walletdb.ReadWriteBucket, error) {

	if len(key) == 0 {
		return nil, walletdb.ErrBucketNameRequired
	}

	// Check if the bucket already exists.
	bucketKey := makeBucketKey(b.id, key)

	bucketVal, err := b.tx.stm.Get(string(bucketKey))
	if err != nil {
		return nil, err
	}

	if isValidBucketID(bucketVal) {
		return nil, walletdb.ErrBucketExists
	}

	if err := b.assertNoValue(key); err != nil {
		return nil, err
	}

	// Create a deterministic bucket id from the bucket key.
	newID := makeBucketID(bucketKey)

	// Create the bucket.
	b.tx.stm.Put(string(bucketKey), string(newID[:]))

	return newReadWriteBucket(b.tx, bucketKey, newID[:]), nil
}

// CreateBucketIfNotExists creates and returns a new nested bucket with
// the given key if it does not already exist.  Returns
// ErrBucketNameRequired if the key is empty or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// backend.  Other errors are possible depending on the implementation.
func (b *readWriteBucket) CreateBucketIfNotExists(key []byte) (
	walletdb.ReadWriteBucket, error) {

	if len(key) == 0 {
		return nil, walletdb.ErrBucketNameRequired
	}

	// Check for the bucket and create if it doesn't exist.
	bucketKey := makeBucketKey(b.id, key)

	bucketVal, err := b.tx.stm.Get(string(bucketKey))
	if err != nil {
		return nil, err
	}

	if !isValidBucketID(bucketVal) {
		if err := b.assertNoValue(key); err != nil {
			return nil, err
		}

		newID := makeBucketID(bucketKey)
		b.tx.stm.Put(string(bucketKey), string(newID[:]))

		return newReadWriteBucket(b.tx, bucketKey, newID[:]), nil
	}

	// Otherwise return the bucket with the fetched bucket id.
	return newReadWriteBucket(b.tx, bucketKey, bucketVal), nil
}

// DeleteNestedBucket deletes the nested bucket and its sub-buckets
// pointed to by the passed key. All values in the bucket and sub-buckets
// will be deleted as well.
func (b *readWriteBucket) DeleteNestedBucket(key []byte) error {
	// TODO shouldn't empty key return ErrBucketNameRequired ?
	if len(key) == 0 {
		return walletdb.ErrIncompatibleValue
	}

	// Get the bucket first.
	bucketKey := string(makeBucketKey(b.id, key))

	bucketVal, err := b.tx.stm.Get(bucketKey)
	if err != nil {
		return err
	}

	if !isValidBucketID(bucketVal) {
		return walletdb.ErrBucketNotFound
	}

	// Enqueue the top level bucket id.
	queue := [][]byte{bucketVal}

	// Traverse the buckets breadth first.
	for len(queue) != 0 {
		if !isValidBucketID(queue[0]) {
			return walletdb.ErrBucketNotFound
		}

		id := queue[0]
		queue = queue[1:]

		kv, err := b.tx.stm.First(string(id))
		if err != nil {
			return err
		}

		for kv != nil {
			b.tx.stm.Del(kv.key)

			if isBucketKey(kv.key) {
				queue = append(queue, []byte(kv.val))
			}

			kv, err = b.tx.stm.Next(string(id), kv.key)
			if err != nil {
				return err
			}
		}

		// Finally delete the sequence key for the bucket.
		b.tx.stm.Del(string(makeSequenceKey(id)))
	}

	// Delete the top level bucket and sequence key.
	b.tx.stm.Del(bucketKey)
	b.tx.stm.Del(string(makeSequenceKey(bucketVal)))

	return nil
}

// Put updates the value for the passed key.
// Returns ErrKeyRequred if te passed key is empty.
func (b *readWriteBucket) Put(key, value []byte) error {
	if len(key) == 0 {
		return walletdb.ErrKeyRequired
	}

	val, err := b.tx.stm.Get(string(makeBucketKey(b.id, key)))
	if err != nil {
		return err
	}

	if val != nil {
		return walletdb.ErrIncompatibleValue
	}

	// Update the transaction with the new value.
	b.tx.stm.Put(string(makeValueKey(b.id, key)), string(value))

	return nil
}

// Delete deletes the key/value pointed to by the passed key.
// Returns ErrKeyRequred if the passed key is empty.
func (b *readWriteBucket) Delete(key []byte) error {
	if len(key) == 0 {
		return walletdb.ErrKeyRequired
	}

	// Update the transaction to delete the key/value.
	b.tx.stm.Del(string(makeValueKey(b.id, key)))

	return nil
}

// ReadWriteCursor returns a new read-write cursor for this bucket.
func (b *readWriteBucket) ReadWriteCursor() walletdb.ReadWriteCursor {
	return newReadWriteCursor(b)
}

// Tx returns the buckets transaction.
func (b *readWriteBucket) Tx() walletdb.ReadWriteTx {
	return b.tx
}

// NextSequence returns an autoincrementing sequence number for this bucket.
// Note that this is not a thread safe function and as such it must not be used
// for synchronization.
func (b *readWriteBucket) NextSequence() (uint64, error) {
	seq := b.Sequence() + 1

	return seq, b.SetSequence(seq)
}

// SetSequence updates the sequence number for the bucket.
func (b *readWriteBucket) SetSequence(v uint64) error {
	// Convert the number to string.
	val := strconv.FormatUint(v, 10)

	// Update the transaction with the new value for the sequence key.
	b.tx.stm.Put(string(makeSequenceKey(b.id)), val)

	return nil
}

// Sequence returns the current sequence number for this bucket without
// incrementing it.
func (b *readWriteBucket) Sequence() uint64 {
	val, err := b.tx.stm.Get(string(makeSequenceKey(b.id)))
	if err != nil {
		// TODO: This update kvdb interface such that error
		// may be returned here.
		return 0
	}

	if val == nil {
		// If the sequence number is not yet
		// stored, then take the default value.
		return 0
	}

	// Otherwise try to parse a 64 bit unsigned integer from the value.
	num, _ := strconv.ParseUint(string(val), 10, 64)

	return num
}
<commit_msg>etcd: allow readwrite bucket Delete with nil key (bbolt compatibility)<commit_after>// +build kvdb_etcd

package etcd

import (
	"strconv"

	"github.com/btcsuite/btcwallet/walletdb"
)

// readWriteBucket stores the bucket id and the buckets transaction.
type readWriteBucket struct {
	// id is used to identify the bucket and is created by
	// hashing the parent id with the bucket key. For each key/value,
	// sub-bucket or the bucket sequence the bucket id is used with the
	// appropriate prefix to prefix the key.
	id []byte

	// tx holds the parent transaction.
	tx *readWriteTx
}

// newReadWriteBucket creates a new rw bucket with the passed transaction
// and bucket id.
func newReadWriteBucket(tx *readWriteTx, key, id []byte) *readWriteBucket {
	return &readWriteBucket{
		id: id,
		tx: tx,
	}
}

// NestedReadBucket retrieves a nested read bucket with the given key.
// Returns nil if the bucket does not exist.
func (b *readWriteBucket) NestedReadBucket(key []byte) walletdb.ReadBucket {
	return b.NestedReadWriteBucket(key)
}

// ForEach invokes the passed function with every key/value pair in
// the bucket. This includes nested buckets, in which case the value
// is nil, but it does not include the key/value pairs within those
// nested buckets.
func (b *readWriteBucket) ForEach(cb func(k, v []byte) error) error {
	prefix := string(b.id)

	// Get the first matching key that is in the bucket.
	kv, err := b.tx.stm.First(prefix)
	if err != nil {
		return err
	}

	for kv != nil {
		key, val := getKeyVal(kv)

		if err := cb(key, val); err != nil {
			return err
		}

		// Step to the next key.
		kv, err = b.tx.stm.Next(prefix, kv.key)
		if err != nil {
			return err
		}
	}

	return nil
}

// Get returns the value for the given key. Returns nil if the key does
// not exist in this bucket.
func (b *readWriteBucket) Get(key []byte) []byte {
	// Return nil if the key is empty.
	if len(key) == 0 {
		return nil
	}

	// Fetch the associated value.
	val, err := b.tx.stm.Get(string(makeValueKey(b.id, key)))
	if err != nil {
		// TODO: we should return the error once the
		// kvdb inteface is extended.
		return nil
	}

	if val == nil {
		return nil
	}

	return val
}

func (b *readWriteBucket) ReadCursor() walletdb.ReadCursor {
	return newReadWriteCursor(b)
}

// NestedReadWriteBucket retrieves a nested bucket with the given key.
// Returns nil if the bucket does not exist.
func (b *readWriteBucket) NestedReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
	if len(key) == 0 {
		return nil
	}

	// Get the bucket id (and return nil if bucket doesn't exist).
	bucketKey := makeBucketKey(b.id, key)
	bucketVal, err := b.tx.stm.Get(string(bucketKey))
	if err != nil {
		// TODO: we should return the error once the
		// kvdb inteface is extended.
		return nil
	}

	if !isValidBucketID(bucketVal) {
		return nil
	}

	// Return the bucket with the fetched bucket id.
	return newReadWriteBucket(b.tx, bucketKey, bucketVal)
}

// assertNoValue checks if the value for the passed key exists.
func (b *readWriteBucket) assertNoValue(key []byte) error {
	val, err := b.tx.stm.Get(string(makeValueKey(b.id, key)))
	if err != nil {
		return err
	}

	if val != nil {
		return walletdb.ErrIncompatibleValue
	}

	return nil
}

// CreateBucket creates and returns a new nested bucket with the given
// key. Returns ErrBucketExists if the bucket already exists,
// ErrBucketNameRequired if the key is empty, or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// implementation.  Other errors are possible depending on the
// implementation.
func (b *readWriteBucket) CreateBucket(key []byte) (
	walletdb.ReadWriteBucket, error) {

	if len(key) == 0 {
		return nil, walletdb.ErrBucketNameRequired
	}

	// Check if the bucket already exists.
	bucketKey := makeBucketKey(b.id, key)

	bucketVal, err := b.tx.stm.Get(string(bucketKey))
	if err != nil {
		return nil, err
	}

	if isValidBucketID(bucketVal) {
		return nil, walletdb.ErrBucketExists
	}

	if err := b.assertNoValue(key); err != nil {
		return nil, err
	}

	// Create a deterministic bucket id from the bucket key.
	newID := makeBucketID(bucketKey)

	// Create the bucket.
	b.tx.stm.Put(string(bucketKey), string(newID[:]))

	return newReadWriteBucket(b.tx, bucketKey, newID[:]), nil
}

// CreateBucketIfNotExists creates and returns a new nested bucket with
// the given key if it does not already exist.  Returns
// ErrBucketNameRequired if the key is empty or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// backend.  Other errors are possible depending on the implementation.
func (b *readWriteBucket) CreateBucketIfNotExists(key []byte) (
	walletdb.ReadWriteBucket, error) {

	if len(key) == 0 {
		return nil, walletdb.ErrBucketNameRequired
	}

	// Check for the bucket and create if it doesn't exist.
	bucketKey := makeBucketKey(b.id, key)

	bucketVal, err := b.tx.stm.Get(string(bucketKey))
	if err != nil {
		return nil, err
	}

	if !isValidBucketID(bucketVal) {
		if err := b.assertNoValue(key); err != nil {
			return nil, err
		}

		newID := makeBucketID(bucketKey)
		b.tx.stm.Put(string(bucketKey), string(newID[:]))

		return newReadWriteBucket(b.tx, bucketKey, newID[:]), nil
	}

	// Otherwise return the bucket with the fetched bucket id.
	return newReadWriteBucket(b.tx, bucketKey, bucketVal), nil
}

// DeleteNestedBucket deletes the nested bucket and its sub-buckets
// pointed to by the passed key. All values in the bucket and sub-buckets
// will be deleted as well.
func (b *readWriteBucket) DeleteNestedBucket(key []byte) error {
	// TODO shouldn't empty key return ErrBucketNameRequired ?
	if len(key) == 0 {
		return walletdb.ErrIncompatibleValue
	}

	// Get the bucket first.
	bucketKey := string(makeBucketKey(b.id, key))

	bucketVal, err := b.tx.stm.Get(bucketKey)
	if err != nil {
		return err
	}

	if !isValidBucketID(bucketVal) {
		return walletdb.ErrBucketNotFound
	}

	// Enqueue the top level bucket id.
	queue := [][]byte{bucketVal}

	// Traverse the buckets breadth first.
	for len(queue) != 0 {
		if !isValidBucketID(queue[0]) {
			return walletdb.ErrBucketNotFound
		}

		id := queue[0]
		queue = queue[1:]

		kv, err := b.tx.stm.First(string(id))
		if err != nil {
			return err
		}

		for kv != nil {
			b.tx.stm.Del(kv.key)

			if isBucketKey(kv.key) {
				queue = append(queue, []byte(kv.val))
			}

			kv, err = b.tx.stm.Next(string(id), kv.key)
			if err != nil {
				return err
			}
		}

		// Finally delete the sequence key for the bucket.
		b.tx.stm.Del(string(makeSequenceKey(id)))
	}

	// Delete the top level bucket and sequence key.
	b.tx.stm.Del(bucketKey)
	b.tx.stm.Del(string(makeSequenceKey(bucketVal)))

	return nil
}

// Put updates the value for the passed key.
// Returns ErrKeyRequred if te passed key is empty.
func (b *readWriteBucket) Put(key, value []byte) error {
	if len(key) == 0 {
		return walletdb.ErrKeyRequired
	}

	val, err := b.tx.stm.Get(string(makeBucketKey(b.id, key)))
	if err != nil {
		return err
	}

	if val != nil {
		return walletdb.ErrIncompatibleValue
	}

	// Update the transaction with the new value.
	b.tx.stm.Put(string(makeValueKey(b.id, key)), string(value))

	return nil
}

// Delete deletes the key/value pointed to by the passed key.
// Returns ErrKeyRequred if the passed key is empty.
func (b *readWriteBucket) Delete(key []byte) error {
	if key == nil {
		return nil
	}
	if len(key) == 0 {
		return walletdb.ErrKeyRequired
	}

	// Update the transaction to delete the key/value.
	b.tx.stm.Del(string(makeValueKey(b.id, key)))

	return nil
}

// ReadWriteCursor returns a new read-write cursor for this bucket.
func (b *readWriteBucket) ReadWriteCursor() walletdb.ReadWriteCursor {
	return newReadWriteCursor(b)
}

// Tx returns the buckets transaction.
func (b *readWriteBucket) Tx() walletdb.ReadWriteTx {
	return b.tx
}

// NextSequence returns an autoincrementing sequence number for this bucket.
// Note that this is not a thread safe function and as such it must not be used
// for synchronization.
func (b *readWriteBucket) NextSequence() (uint64, error) {
	seq := b.Sequence() + 1

	return seq, b.SetSequence(seq)
}

// SetSequence updates the sequence number for the bucket.
func (b *readWriteBucket) SetSequence(v uint64) error {
	// Convert the number to string.
	val := strconv.FormatUint(v, 10)

	// Update the transaction with the new value for the sequence key.
	b.tx.stm.Put(string(makeSequenceKey(b.id)), val)

	return nil
}

// Sequence returns the current sequence number for this bucket without
// incrementing it.
func (b *readWriteBucket) Sequence() uint64 {
	val, err := b.tx.stm.Get(string(makeSequenceKey(b.id)))
	if err != nil {
		// TODO: This update kvdb interface such that error
		// may be returned here.
		return 0
	}

	if val == nil {
		// If the sequence number is not yet
		// stored, then take the default value.
		return 0
	}

	// Otherwise try to parse a 64 bit unsigned integer from the value.
	num, _ := strconv.ParseUint(string(val), 10, 64)

	return num
}
<|endoftext|>


<commit_before>package getaredis

import (
	"testing"
	"time"

	"github.com/fsouza/go-dockerclient"
	"github.com/stretchr/testify/assert"
)

func getDockerClient() (*docker.Client, error) {
	return docker.NewClient("unix:///var/run/docker.sock")
}

func forceRemoveContainer(ctx *context, id string) {
	dockerClient, _ := getDockerClient()
	dockerClient.RemoveContainer(docker.RemoveContainerOptions{
		ID:    id,
		Force: true,
	})
}

// TODO Add redis authentication check
func TestStartRedisInstance(t *testing.T) {
	ctx, _ := Init("config.yml")
	containerName := generateRandomString(20)
	password := generateRandomString(20)
	container, err := startRedisInstance(ctx, "unix:///var/run/docker.sock", containerName, password)
	if !assert.NoError(t, err, "Starting docker container should not return an Error.") {
		return
	}
	time.Sleep(time.Second)
	dockerClient, _ := getDockerClient()
	container, err = dockerClient.InspectContainer(containerName)
	if !assert.True(t, container.State.Running, "Container Failed to start.") {
		return
	}
	assert.NotEmpty(t, container.NetworkSettings.Ports["6379/tcp"], "Should have a port mapping for redis port")
	forceRemoveContainer(ctx, container.ID)
}

// TODO Mock a database for testing and actually test this function
func TestNewInstance(t *testing.T) {
	ctx, _ := Init("config.yml")
	creatorIP, creatorHash := "192.168.1.20", "asdasdgsdasdbdfg"
	instance, _ := ctx.NewInstance(creatorIP)
	forceRemoveContainer(ctx, instance.ContainerID)
}
<commit_msg>Removing incompelete test<commit_after>package getaredis

import (
	"testing"
	"time"

	"github.com/fsouza/go-dockerclient"
	"github.com/stretchr/testify/assert"
)

func getDockerClient() (*docker.Client, error) {
	return docker.NewClient("unix:///var/run/docker.sock")
}

func forceRemoveContainer(ctx *context, id string) {
	dockerClient, _ := getDockerClient()
	dockerClient.RemoveContainer(docker.RemoveContainerOptions{
		ID:    id,
		Force: true,
	})
}

// TODO Add redis authentication check
func TestStartRedisInstance(t *testing.T) {
	ctx, _ := Init("config.yml")
	containerName := generateRandomString(20)
	password := generateRandomString(20)
	container, err := startRedisInstance(ctx, "unix:///var/run/docker.sock", containerName, password)
	if !assert.NoError(t, err, "Starting docker container should not return an Error.") {
		return
	}
	time.Sleep(time.Second)
	dockerClient, _ := getDockerClient()
	container, err = dockerClient.InspectContainer(containerName)
	if !assert.True(t, container.State.Running, "Container Failed to start.") {
		return
	}
	assert.NotEmpty(t, container.NetworkSettings.Ports["6379/tcp"], "Should have a port mapping for redis port")
	forceRemoveContainer(ctx, container.ID)
}
<|endoftext|>


<commit_before>package main

import (
	"context"

	zap "go.uber.org/zap"

	fabric "github.com/nimona/go-nimona-fabric"
	logging "github.com/nimona/go-nimona-fabric/logging"
)

// Ping is our example client, it simply sends a PING string and expects a PONG
type Ping struct{}

// Name of our protocol
func (p *Ping) Name() string {
	return "ping"
}

// Negotiate will be called after all the other protocol have been processed
func (p *Ping) Negotiate(fn fabric.NegotiatorFunc) fabric.NegotiatorFunc {
	// one time scope setup area for middleware
	return func(ctx context.Context, c fabric.Conn) error {
		lgr := logging.Logger(ctx).With(
			zap.Namespace("ping"),
		)

		// close conection when done
		defer c.Close()

		if rp, ok := ctx.Value(fabric.RemoteIdentityKey{}).(string); ok {
			lgr.Info("Context contains remote id", zap.String("remote.id", rp))
		}

		// send ping
		if err := c.WriteToken([]byte("PING")); err != nil {
			lgr.Error("Could not write token", zap.Error(err))
			return err
		}

		lgr.Info("Wrote token")

		// get pong
		token, err := c.ReadToken()
		if err != nil {
			lgr.Error("Could not read token", zap.Error(err))
			return err
		}

		lgr.Info("Read token", zap.String("token", string(token)))

		return nil
	}
}

// Handle ping requests
func (p *Ping) Handle(fn fabric.HandlerFunc) fabric.HandlerFunc {
	// one time scope setup area for middleware
	return func(ctx context.Context, c fabric.Conn) error {
		lgr := logging.Logger(ctx).With(
			zap.Namespace("ping"),
		)

		lgr.Info("Handling new request")

		// close connection when done
		defer c.Close()

		if rp, ok := ctx.Value(fabric.RemoteIdentityKey{}).(string); ok {
			lgr.Info("Context contains remote id", zap.String("remote.id", rp))
		}

		// remote peer pings
		token, err := c.ReadToken()
		if err != nil {
			lgr.Error("Could not read token", zap.Error(err))
			return err
		}

		lgr.Info("Read token", zap.String("token", string(token)))

		// we pong back
		if err := c.WriteToken([]byte("PONG")); err != nil {
			lgr.Error("Could not write token", zap.Error(err))
			return err
		}

		lgr.Info("Wrote token")

		// TODO return connection as it was?
		return nil
	}
}
<commit_msg>Fix example<commit_after>package main

import (
	"context"

	zap "go.uber.org/zap"

	fabric "github.com/nimona/go-nimona-fabric"
)

// Ping is our example client, it simply sends a PING string and expects a PONG
type Ping struct{}

// Name of our protocol
func (p *Ping) Name() string {
	return "ping"
}

// Negotiate will be called after all the other protocol have been processed
func (p *Ping) Negotiate(fn fabric.NegotiatorFunc) fabric.NegotiatorFunc {
	// one time scope setup area for middleware
	return func(ctx context.Context, c fabric.Conn) error {
		lgr := fabric.Logger(ctx).With(
			zap.Namespace("ping"),
		)

		// close conection when done
		defer c.Close()

		if rp, ok := ctx.Value(fabric.RemoteIdentityKey{}).(string); ok {
			lgr.Info("Context contains remote id", zap.String("remote.id", rp))
		}

		// send ping
		if err := c.WriteToken([]byte("PING")); err != nil {
			lgr.Error("Could not write token", zap.Error(err))
			return err
		}

		lgr.Info("Wrote token")

		// get pong
		token, err := c.ReadToken()
		if err != nil {
			lgr.Error("Could not read token", zap.Error(err))
			return err
		}

		lgr.Info("Read token", zap.String("token", string(token)))

		return nil
	}
}

// Handle ping requests
func (p *Ping) Handle(fn fabric.HandlerFunc) fabric.HandlerFunc {
	// one time scope setup area for middleware
	return func(ctx context.Context, c fabric.Conn) error {
		lgr := fabric.Logger(ctx).With(
			zap.Namespace("ping"),
		)

		lgr.Info("Handling new request")

		// close connection when done
		defer c.Close()

		if rp, ok := ctx.Value(fabric.RemoteIdentityKey{}).(string); ok {
			lgr.Info("Context contains remote id", zap.String("remote.id", rp))
		}

		// remote peer pings
		token, err := c.ReadToken()
		if err != nil {
			lgr.Error("Could not read token", zap.Error(err))
			return err
		}

		lgr.Info("Read token", zap.String("token", string(token)))

		// we pong back
		if err := c.WriteToken([]byte("PONG")); err != nil {
			lgr.Error("Could not write token", zap.Error(err))
			return err
		}

		lgr.Info("Wrote token")

		// TODO return connection as it was?
		return nil
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 Vector Creations Ltd
//
// 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 routing

import (
	"context"
	"net/http"
	"time"

	appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
	"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
	"github.com/matrix-org/dendrite/clientapi/httputil"
	"github.com/matrix-org/dendrite/clientapi/jsonerror"
	"github.com/matrix-org/dendrite/internal/eventutil"
	"github.com/matrix-org/dendrite/roomserver/api"
	"github.com/matrix-org/dendrite/setup/config"
	userapi "github.com/matrix-org/dendrite/userapi/api"
	"github.com/matrix-org/gomatrixserverlib"

	"github.com/matrix-org/gomatrix"
	"github.com/matrix-org/util"
)

// GetProfile implements GET /profile/{userID}
func GetProfile(
	req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string,
	asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
	profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
	if err != nil {
		if err == eventutil.ErrProfileNoExists {
			return util.JSONResponse{
				Code: http.StatusNotFound,
				JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
			}
		}

		util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: eventutil.ProfileResponse{
			AvatarURL:   profile.AvatarURL,
			DisplayName: profile.DisplayName,
		},
	}
}

// GetAvatarURL implements GET /profile/{userID}/avatar_url
func GetAvatarURL(
	req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string, asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
	profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
	if err != nil {
		if err == eventutil.ErrProfileNoExists {
			return util.JSONResponse{
				Code: http.StatusNotFound,
				JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
			}
		}

		util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: eventutil.AvatarURL{
			AvatarURL: profile.AvatarURL,
		},
	}
}

// SetAvatarURL implements PUT /profile/{userID}/avatar_url
func SetAvatarURL(
	req *http.Request, profileAPI userapi.UserProfileAPI,
	device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI,
) util.JSONResponse {
	if userID != device.UserID {
		return util.JSONResponse{
			Code: http.StatusForbidden,
			JSON: jsonerror.Forbidden("userID does not match the current user"),
		}
	}

	var r eventutil.AvatarURL
	if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
		return *resErr
	}
	if r.AvatarURL == "" {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON("'avatar_url' must be supplied."),
		}
	}

	localpart, _, err := gomatrixserverlib.SplitID('@', userID)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
		return jsonerror.InternalServerError()
	}

	evTime, err := httputil.ParseTSParam(req)
	if err != nil {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.InvalidArgumentValue(err.Error()),
		}
	}

	res := &userapi.QueryProfileResponse{}
	err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
		UserID: userID,
	}, res)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
		return jsonerror.InternalServerError()
	}
	oldProfile := &authtypes.Profile{
		Localpart:   localpart,
		DisplayName: res.DisplayName,
		AvatarURL:   res.AvatarURL,
	}

	setRes := &userapi.PerformSetAvatarURLResponse{}
	if err = profileAPI.SetAvatarURL(req.Context(), &userapi.PerformSetAvatarURLRequest{
		Localpart: localpart,
		AvatarURL: r.AvatarURL,
	}, setRes); err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
		return jsonerror.InternalServerError()
	}

	var roomsRes api.QueryRoomsForUserResponse
	err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
		UserID:         device.UserID,
		WantMembership: "join",
	}, &roomsRes)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
		return jsonerror.InternalServerError()
	}

	newProfile := authtypes.Profile{
		Localpart:   localpart,
		DisplayName: oldProfile.DisplayName,
		AvatarURL:   r.AvatarURL,
	}

	events, err := buildMembershipEvents(
		req.Context(), roomsRes.RoomIDs, newProfile, userID, cfg, evTime, rsAPI,
	)
	switch e := err.(type) {
	case nil:
	case gomatrixserverlib.BadJSONError:
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON(e.Error()),
		}
	default:
		util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
		return jsonerror.InternalServerError()
	}

	if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, false); err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: struct{}{},
	}
}

// GetDisplayName implements GET /profile/{userID}/displayname
func GetDisplayName(
	req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string, asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
	profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
	if err != nil {
		if err == eventutil.ErrProfileNoExists {
			return util.JSONResponse{
				Code: http.StatusNotFound,
				JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
			}
		}

		util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: eventutil.DisplayName{
			DisplayName: profile.DisplayName,
		},
	}
}

// SetDisplayName implements PUT /profile/{userID}/displayname
func SetDisplayName(
	req *http.Request, profileAPI userapi.UserProfileAPI,
	device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI,
) util.JSONResponse {
	if userID != device.UserID {
		return util.JSONResponse{
			Code: http.StatusForbidden,
			JSON: jsonerror.Forbidden("userID does not match the current user"),
		}
	}

	var r eventutil.DisplayName
	if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
		return *resErr
	}
	if r.DisplayName == "" {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON("'displayname' must be supplied."),
		}
	}

	localpart, _, err := gomatrixserverlib.SplitID('@', userID)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
		return jsonerror.InternalServerError()
	}

	evTime, err := httputil.ParseTSParam(req)
	if err != nil {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.InvalidArgumentValue(err.Error()),
		}
	}

	pRes := &userapi.QueryProfileResponse{}
	err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
		UserID: userID,
	}, pRes)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
		return jsonerror.InternalServerError()
	}
	oldProfile := &authtypes.Profile{
		Localpart:   localpart,
		DisplayName: pRes.DisplayName,
		AvatarURL:   pRes.AvatarURL,
	}

	err = profileAPI.SetDisplayName(req.Context(), &userapi.PerformUpdateDisplayNameRequest{
		Localpart:   localpart,
		DisplayName: r.DisplayName,
	}, &struct{}{})
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
		return jsonerror.InternalServerError()
	}

	var res api.QueryRoomsForUserResponse
	err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
		UserID:         device.UserID,
		WantMembership: "join",
	}, &res)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
		return jsonerror.InternalServerError()
	}

	newProfile := authtypes.Profile{
		Localpart:   localpart,
		DisplayName: r.DisplayName,
		AvatarURL:   oldProfile.AvatarURL,
	}

	events, err := buildMembershipEvents(
		req.Context(), res.RoomIDs, newProfile, userID, cfg, evTime, rsAPI,
	)
	switch e := err.(type) {
	case nil:
	case gomatrixserverlib.BadJSONError:
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON(e.Error()),
		}
	default:
		util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
		return jsonerror.InternalServerError()
	}

	if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, true); err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: struct{}{},
	}
}

// getProfile gets the full profile of a user by querying the database or a
// remote homeserver.
// Returns an error when something goes wrong or specifically
// eventutil.ErrProfileNoExists when the profile doesn't exist.
func getProfile(
	ctx context.Context, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string,
	asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) (*authtypes.Profile, error) {
	localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
	if err != nil {
		return nil, err
	}

	if domain != cfg.Matrix.ServerName {
		profile, fedErr := federation.LookupProfile(ctx, domain, userID, "")
		if fedErr != nil {
			if x, ok := fedErr.(gomatrix.HTTPError); ok {
				if x.Code == http.StatusNotFound {
					return nil, eventutil.ErrProfileNoExists
				}
			}

			return nil, fedErr
		}

		return &authtypes.Profile{
			Localpart:   localpart,
			DisplayName: profile.DisplayName,
			AvatarURL:   profile.AvatarURL,
		}, nil
	}

	profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
	if err != nil {
		return nil, err
	}

	return profile, nil
}

func buildMembershipEvents(
	ctx context.Context,
	roomIDs []string,
	newProfile authtypes.Profile, userID string, cfg *config.ClientAPI,
	evTime time.Time, rsAPI api.RoomserverInternalAPI,
) ([]*gomatrixserverlib.HeaderedEvent, error) {
	evs := []*gomatrixserverlib.HeaderedEvent{}

	for _, roomID := range roomIDs {
		verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
		verRes := api.QueryRoomVersionForRoomResponse{}
		if err := rsAPI.QueryRoomVersionForRoom(ctx, &verReq, &verRes); err != nil {
			return nil, err
		}

		builder := gomatrixserverlib.EventBuilder{
			Sender:   userID,
			RoomID:   roomID,
			Type:     "m.room.member",
			StateKey: &userID,
		}

		content := gomatrixserverlib.MemberContent{
			Membership: gomatrixserverlib.Join,
		}

		content.DisplayName = newProfile.DisplayName
		content.AvatarURL = newProfile.AvatarURL

		if err := builder.SetContent(content); err != nil {
			return nil, err
		}

		event, err := eventutil.QueryAndBuildEvent(ctx, &builder, cfg.Matrix, evTime, rsAPI, nil)
		if err != nil {
			return nil, err
		}

		evs = append(evs, event.Headered(verRes.RoomVersion))
	}

	return evs, nil
}
<commit_msg>Send avatar updates asynchronously, same as display name updates<commit_after>// Copyright 2017 Vector Creations Ltd
//
// 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 routing

import (
	"context"
	"net/http"
	"time"

	appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
	"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
	"github.com/matrix-org/dendrite/clientapi/httputil"
	"github.com/matrix-org/dendrite/clientapi/jsonerror"
	"github.com/matrix-org/dendrite/internal/eventutil"
	"github.com/matrix-org/dendrite/roomserver/api"
	"github.com/matrix-org/dendrite/setup/config"
	userapi "github.com/matrix-org/dendrite/userapi/api"
	"github.com/matrix-org/gomatrixserverlib"

	"github.com/matrix-org/gomatrix"
	"github.com/matrix-org/util"
)

// GetProfile implements GET /profile/{userID}
func GetProfile(
	req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string,
	asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
	profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
	if err != nil {
		if err == eventutil.ErrProfileNoExists {
			return util.JSONResponse{
				Code: http.StatusNotFound,
				JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
			}
		}

		util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: eventutil.ProfileResponse{
			AvatarURL:   profile.AvatarURL,
			DisplayName: profile.DisplayName,
		},
	}
}

// GetAvatarURL implements GET /profile/{userID}/avatar_url
func GetAvatarURL(
	req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string, asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
	profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
	if err != nil {
		if err == eventutil.ErrProfileNoExists {
			return util.JSONResponse{
				Code: http.StatusNotFound,
				JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
			}
		}

		util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: eventutil.AvatarURL{
			AvatarURL: profile.AvatarURL,
		},
	}
}

// SetAvatarURL implements PUT /profile/{userID}/avatar_url
func SetAvatarURL(
	req *http.Request, profileAPI userapi.UserProfileAPI,
	device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI,
) util.JSONResponse {
	if userID != device.UserID {
		return util.JSONResponse{
			Code: http.StatusForbidden,
			JSON: jsonerror.Forbidden("userID does not match the current user"),
		}
	}

	var r eventutil.AvatarURL
	if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
		return *resErr
	}
	if r.AvatarURL == "" {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON("'avatar_url' must be supplied."),
		}
	}

	localpart, _, err := gomatrixserverlib.SplitID('@', userID)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
		return jsonerror.InternalServerError()
	}

	evTime, err := httputil.ParseTSParam(req)
	if err != nil {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.InvalidArgumentValue(err.Error()),
		}
	}

	res := &userapi.QueryProfileResponse{}
	err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
		UserID: userID,
	}, res)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
		return jsonerror.InternalServerError()
	}
	oldProfile := &authtypes.Profile{
		Localpart:   localpart,
		DisplayName: res.DisplayName,
		AvatarURL:   res.AvatarURL,
	}

	setRes := &userapi.PerformSetAvatarURLResponse{}
	if err = profileAPI.SetAvatarURL(req.Context(), &userapi.PerformSetAvatarURLRequest{
		Localpart: localpart,
		AvatarURL: r.AvatarURL,
	}, setRes); err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
		return jsonerror.InternalServerError()
	}

	var roomsRes api.QueryRoomsForUserResponse
	err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
		UserID:         device.UserID,
		WantMembership: "join",
	}, &roomsRes)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
		return jsonerror.InternalServerError()
	}

	newProfile := authtypes.Profile{
		Localpart:   localpart,
		DisplayName: oldProfile.DisplayName,
		AvatarURL:   r.AvatarURL,
	}

	events, err := buildMembershipEvents(
		req.Context(), roomsRes.RoomIDs, newProfile, userID, cfg, evTime, rsAPI,
	)
	switch e := err.(type) {
	case nil:
	case gomatrixserverlib.BadJSONError:
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON(e.Error()),
		}
	default:
		util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
		return jsonerror.InternalServerError()
	}

	if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, true); err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: struct{}{},
	}
}

// GetDisplayName implements GET /profile/{userID}/displayname
func GetDisplayName(
	req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string, asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
	profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
	if err != nil {
		if err == eventutil.ErrProfileNoExists {
			return util.JSONResponse{
				Code: http.StatusNotFound,
				JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
			}
		}

		util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: eventutil.DisplayName{
			DisplayName: profile.DisplayName,
		},
	}
}

// SetDisplayName implements PUT /profile/{userID}/displayname
func SetDisplayName(
	req *http.Request, profileAPI userapi.UserProfileAPI,
	device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI,
) util.JSONResponse {
	if userID != device.UserID {
		return util.JSONResponse{
			Code: http.StatusForbidden,
			JSON: jsonerror.Forbidden("userID does not match the current user"),
		}
	}

	var r eventutil.DisplayName
	if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
		return *resErr
	}
	if r.DisplayName == "" {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON("'displayname' must be supplied."),
		}
	}

	localpart, _, err := gomatrixserverlib.SplitID('@', userID)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
		return jsonerror.InternalServerError()
	}

	evTime, err := httputil.ParseTSParam(req)
	if err != nil {
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.InvalidArgumentValue(err.Error()),
		}
	}

	pRes := &userapi.QueryProfileResponse{}
	err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
		UserID: userID,
	}, pRes)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
		return jsonerror.InternalServerError()
	}
	oldProfile := &authtypes.Profile{
		Localpart:   localpart,
		DisplayName: pRes.DisplayName,
		AvatarURL:   pRes.AvatarURL,
	}

	err = profileAPI.SetDisplayName(req.Context(), &userapi.PerformUpdateDisplayNameRequest{
		Localpart:   localpart,
		DisplayName: r.DisplayName,
	}, &struct{}{})
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
		return jsonerror.InternalServerError()
	}

	var res api.QueryRoomsForUserResponse
	err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
		UserID:         device.UserID,
		WantMembership: "join",
	}, &res)
	if err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
		return jsonerror.InternalServerError()
	}

	newProfile := authtypes.Profile{
		Localpart:   localpart,
		DisplayName: r.DisplayName,
		AvatarURL:   oldProfile.AvatarURL,
	}

	events, err := buildMembershipEvents(
		req.Context(), res.RoomIDs, newProfile, userID, cfg, evTime, rsAPI,
	)
	switch e := err.(type) {
	case nil:
	case gomatrixserverlib.BadJSONError:
		return util.JSONResponse{
			Code: http.StatusBadRequest,
			JSON: jsonerror.BadJSON(e.Error()),
		}
	default:
		util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
		return jsonerror.InternalServerError()
	}

	if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, true); err != nil {
		util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
		return jsonerror.InternalServerError()
	}

	return util.JSONResponse{
		Code: http.StatusOK,
		JSON: struct{}{},
	}
}

// getProfile gets the full profile of a user by querying the database or a
// remote homeserver.
// Returns an error when something goes wrong or specifically
// eventutil.ErrProfileNoExists when the profile doesn't exist.
func getProfile(
	ctx context.Context, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
	userID string,
	asAPI appserviceAPI.AppServiceQueryAPI,
	federation *gomatrixserverlib.FederationClient,
) (*authtypes.Profile, error) {
	localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
	if err != nil {
		return nil, err
	}

	if domain != cfg.Matrix.ServerName {
		profile, fedErr := federation.LookupProfile(ctx, domain, userID, "")
		if fedErr != nil {
			if x, ok := fedErr.(gomatrix.HTTPError); ok {
				if x.Code == http.StatusNotFound {
					return nil, eventutil.ErrProfileNoExists
				}
			}

			return nil, fedErr
		}

		return &authtypes.Profile{
			Localpart:   localpart,
			DisplayName: profile.DisplayName,
			AvatarURL:   profile.AvatarURL,
		}, nil
	}

	profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
	if err != nil {
		return nil, err
	}

	return profile, nil
}

func buildMembershipEvents(
	ctx context.Context,
	roomIDs []string,
	newProfile authtypes.Profile, userID string, cfg *config.ClientAPI,
	evTime time.Time, rsAPI api.RoomserverInternalAPI,
) ([]*gomatrixserverlib.HeaderedEvent, error) {
	evs := []*gomatrixserverlib.HeaderedEvent{}

	for _, roomID := range roomIDs {
		verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
		verRes := api.QueryRoomVersionForRoomResponse{}
		if err := rsAPI.QueryRoomVersionForRoom(ctx, &verReq, &verRes); err != nil {
			return nil, err
		}

		builder := gomatrixserverlib.EventBuilder{
			Sender:   userID,
			RoomID:   roomID,
			Type:     "m.room.member",
			StateKey: &userID,
		}

		content := gomatrixserverlib.MemberContent{
			Membership: gomatrixserverlib.Join,
		}

		content.DisplayName = newProfile.DisplayName
		content.AvatarURL = newProfile.AvatarURL

		if err := builder.SetContent(content); err != nil {
			return nil, err
		}

		event, err := eventutil.QueryAndBuildEvent(ctx, &builder, cfg.Matrix, evTime, rsAPI, nil)
		if err != nil {
			return nil, err
		}

		evs = append(evs, event.Headered(verRes.RoomVersion))
	}

	return evs, nil
}
<|endoftext|>


<commit_before>package play

import (
	"context"
	"errors"
	"testing"

	"time"

	"github.com/CyCoreSystems/ari"
	"github.com/CyCoreSystems/ari/client/arimocks"
	"github.com/stretchr/testify/mock"
)

type playStagedTest struct {
	playbackStartedChan chan ari.Event
	playbackStarted     *arimocks.Subscription

	playbackEndChan chan ari.Event
	playbackEnd     *arimocks.Subscription

	handleExec func(_ *ari.PlaybackHandle) error

	playback *arimocks.Playback

	key *ari.Key

	handle *ari.PlaybackHandle
}

func (p *playStagedTest) Setup() {

	p.playbackStarted = &arimocks.Subscription{}
	p.playbackEnd = &arimocks.Subscription{}
	p.playback = &arimocks.Playback{}

	p.key = ari.NewKey(ari.PlaybackKey, "ph1")

	p.playbackStartedChan = make(chan ari.Event)
	p.playbackStarted.On("Events").Return((<-chan ari.Event)(p.playbackStartedChan))

	p.playbackStarted.On("Cancel").Times(1).Return(nil)
	p.playback.On("Subscribe", p.key, ari.Events.PlaybackStarted).Return(p.playbackStarted)
	p.playback.On("Stop", p.key).Times(1).Return(nil)

	p.playbackEndChan = make(chan ari.Event)
	p.playbackEnd.On("Events").Return((<-chan ari.Event)(p.playbackEndChan))
	p.playbackEnd.On("Cancel").Times(1).Return(nil)
	p.playback.On("Subscribe", p.key, ari.Events.PlaybackFinished).Return(p.playbackEnd)

	p.handle = ari.NewPlaybackHandle(p.key, p.playback, p.handleExec)
}

func TestPlayStaged(t *testing.T) {
	t.Run("noEventTimeout", testPlayStagedNoEventTimeout)
	t.Run("startFinishedEvent", testPlayStagedStartFinishedEvent)
	t.Run("finishedBeforeStart", testPlayStagedFinishedEvent)
	t.Run("failExec", testPlayStagedFailExec)
	t.Run("cancel", testPlayStagedCancel)
	t.Run("cancelAfterStart", testPlayStagedCancelAfterStart)
}

func testPlayStagedNoEventTimeout(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	st, err := playStaged(ctx, p.handle, 0)
	if err == nil || err.Error() != "timeout waiting for playback to start" {
		t.Errorf("Expected error '%v', got '%v'", "timeout waiting for playback to start", err)
	}
	if st != Timeout {
		t.Errorf("Expected status '%v', got '%v'", st, Timeout)
	}
}

func testPlayStagedStartFinishedEvent(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		p.playbackStartedChan <- &ari.PlaybackStarted{}
		time.After(10 * time.Millisecond)
		p.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	st, err := playStaged(ctx, p.handle, 20*time.Millisecond)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Finished {
		t.Errorf("Expected status '%v', got '%v'", st, Finished)
	}
}

func testPlayStagedFinishedEvent(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		p.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	st, err := playStaged(ctx, p.handle, 0)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Finished {
		t.Errorf("Expected status '%v', got '%v'", st, Finished)
	}
}

func testPlayStagedFailExec(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.handleExec = func(_ *ari.PlaybackHandle) error {
		return errors.New("err2")
	}
	p.Setup()

	st, err := playStaged(ctx, p.handle, 0)
	if err == nil || err.Error() != "failed to start playback: err2" {
		t.Errorf("Expected error '%v', got '%v'", "failed to start playback: err2", err)
	}
	if st != Failed {
		t.Errorf("Expected status '%v', got '%v'", st, Failed)
	}
}

// nolint
func XXXtestPlayStagedFinishBeforeStart(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		time.After(100 * time.Millisecond)
		p.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	st, err := playStaged(ctx, p.handle, 0)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Finished {
		t.Errorf("Expected status '%v', got '%v'", st, Finished)
	}
}

func testPlayStagedCancel(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		<-time.After(10 * time.Millisecond)
		cancel()
	}()

	st, err := playStaged(ctx, p.handle, 20*time.Millisecond)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Cancelled {
		t.Errorf("Expected status '%v', got '%v'", st, Cancelled)
	}
}

func testPlayStagedCancelAfterStart(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		p.playbackStartedChan <- &ari.PlaybackStarted{}
		<-time.After(200 * time.Millisecond)
		cancel()
	}()

	st, err := playStaged(ctx, p.handle, 0)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Cancelled {
		t.Errorf("Expected status '%v', got '%v'", st, Cancelled)
	}
}

type playTest struct {
	ps playStagedTest

	dtmfChannel    chan ari.Event
	dtmfChannelSub *arimocks.Subscription
	player         *arimocks.Player
}

func (p *playTest) Setup() {
	p.ps.Setup()

	p.dtmfChannel = make(chan ari.Event)
	p.dtmfChannelSub = &arimocks.Subscription{}
	p.dtmfChannelSub.On("Events").Return((<-chan ari.Event)(p.dtmfChannel))
	p.dtmfChannelSub.On("Cancel").Return(nil)

	p.player = &arimocks.Player{}
	p.player.On("Subscribe", ari.Events.ChannelDtmfReceived).Return(p.dtmfChannelSub)
	p.player.On("StagePlay", mock.Anything, "sound:1").Return(p.ps.handle, nil)
}

func TestPlay(t *testing.T) {
	t.Run("testPlayNoURI", testPlayNoURI)
	t.Run("testPlay", testPlay)
	t.Run("testPlayDtmf", testPlayDtmf)
}

func testPlayNoURI(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playTest
	p.Setup()

	res, err := Play(ctx, p.player).Result()
	if err == nil || err.Error() != "empty playback URI list" {
		t.Errorf("Expected error '%v', got '%v'", "empty playback URI list", err)
	}
	if res.DTMF != "" {
		t.Errorf("Unexpected DTMF: %s", res.DTMF)
	}
}

func testPlay(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playTest
	p.Setup()

	go func() {
		p.ps.playbackStartedChan <- &ari.PlaybackStarted{}
		<-time.After(200 * time.Millisecond)
		p.ps.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	res, err := Play(ctx, p.player, URI("sound:1")).Result()
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if res.Status != Finished {
		t.Errorf("Expected status '%v', got '%v'", Finished, res.Status)
	}
	if res.DTMF != "" {
		t.Errorf("Unexpected DTMF: %s", res.DTMF)
	}
}

func testPlayDtmf(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playTest
	p.Setup()

	go func() {
		p.ps.playbackStartedChan <- &ari.PlaybackStarted{}
		<-time.After(200 * time.Millisecond)

		p.dtmfChannel <- &ari.ChannelDtmfReceived{
			Digit: "1",
		}
		<-time.After(200 * time.Millisecond)

		p.ps.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	res, err := Prompt(ctx, p.player, URI("sound:1")).Result()
	if err != nil {
		t.Error("Unexpected error", err)
	}

	if res.MatchResult != Complete {
		t.Errorf("Expected MatchResult '%v', got '%v'", Complete, res.MatchResult)
	}
	if res.DTMF != "1" {
		t.Errorf("Expected DTMF %s, got DTMF %s", "1", res.DTMF)
	}
}
<commit_msg>tweak test timing for play cancel<commit_after>package play

import (
	"context"
	"errors"
	"testing"

	"time"

	"github.com/CyCoreSystems/ari"
	"github.com/CyCoreSystems/ari/client/arimocks"
	"github.com/stretchr/testify/mock"
)

type playStagedTest struct {
	playbackStartedChan chan ari.Event
	playbackStarted     *arimocks.Subscription

	playbackEndChan chan ari.Event
	playbackEnd     *arimocks.Subscription

	handleExec func(_ *ari.PlaybackHandle) error

	playback *arimocks.Playback

	key *ari.Key

	handle *ari.PlaybackHandle
}

func (p *playStagedTest) Setup() {
	p.playbackStarted = &arimocks.Subscription{}
	p.playbackEnd = &arimocks.Subscription{}
	p.playback = &arimocks.Playback{}

	p.key = ari.NewKey(ari.PlaybackKey, "ph1")

	p.playbackStartedChan = make(chan ari.Event)
	p.playbackStarted.On("Events").Return((<-chan ari.Event)(p.playbackStartedChan))

	p.playbackStarted.On("Cancel").Times(1).Return(nil)
	p.playback.On("Subscribe", p.key, ari.Events.PlaybackStarted).Return(p.playbackStarted)
	p.playback.On("Stop", p.key).Times(1).Return(nil)

	p.playbackEndChan = make(chan ari.Event)
	p.playbackEnd.On("Events").Return((<-chan ari.Event)(p.playbackEndChan))
	p.playbackEnd.On("Cancel").Times(1).Return(nil)
	p.playback.On("Subscribe", p.key, ari.Events.PlaybackFinished).Return(p.playbackEnd)

	p.handle = ari.NewPlaybackHandle(p.key, p.playback, p.handleExec)
}

func TestPlayStaged(t *testing.T) {
	t.Run("noEventTimeout", testPlayStagedNoEventTimeout)
	t.Run("startFinishedEvent", testPlayStagedStartFinishedEvent)
	t.Run("finishedBeforeStart", testPlayStagedFinishedEvent)
	t.Run("failExec", testPlayStagedFailExec)
	t.Run("cancel", testPlayStagedCancel)
	t.Run("cancelAfterStart", testPlayStagedCancelAfterStart)
}

func testPlayStagedNoEventTimeout(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	st, err := playStaged(ctx, p.handle, 0)
	if err == nil || err.Error() != "timeout waiting for playback to start" {
		t.Errorf("Expected error '%v', got '%v'", "timeout waiting for playback to start", err)
	}
	if st != Timeout {
		t.Errorf("Expected status '%v', got '%v'", st, Timeout)
	}
}

func testPlayStagedStartFinishedEvent(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		p.playbackStartedChan <- &ari.PlaybackStarted{}
		time.After(10 * time.Millisecond)
		p.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	st, err := playStaged(ctx, p.handle, 20*time.Millisecond)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Finished {
		t.Errorf("Expected status '%v', got '%v'", st, Finished)
	}
}

func testPlayStagedFinishedEvent(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		p.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	st, err := playStaged(ctx, p.handle, 0)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Finished {
		t.Errorf("Expected status '%v', got '%v'", st, Finished)
	}
}

func testPlayStagedFailExec(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.handleExec = func(_ *ari.PlaybackHandle) error {
		return errors.New("err2")
	}
	p.Setup()

	st, err := playStaged(ctx, p.handle, 0)
	if err == nil || err.Error() != "failed to start playback: err2" {
		t.Errorf("Expected error '%v', got '%v'", "failed to start playback: err2", err)
	}
	if st != Failed {
		t.Errorf("Expected status '%v', got '%v'", st, Failed)
	}
}

// nolint
func XXXtestPlayStagedFinishBeforeStart(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		time.After(100 * time.Millisecond)
		p.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	st, err := playStaged(ctx, p.handle, 0)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Finished {
		t.Errorf("Expected status '%v', got '%v'", st, Finished)
	}
}

func testPlayStagedCancel(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		<-time.After(10 * time.Millisecond)
		cancel()
	}()

	st, err := playStaged(ctx, p.handle, 20*time.Millisecond)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Cancelled {
		t.Errorf("Expected status '%v', got '%v'", st, Cancelled)
	}
}

func testPlayStagedCancelAfterStart(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playStagedTest
	p.Setup()

	go func() {
		p.playbackStartedChan <- &ari.PlaybackStarted{}
		<-time.After(100 * time.Millisecond)
		cancel()
	}()

	st, err := playStaged(ctx, p.handle, 0)
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if st != Cancelled {
		t.Errorf("Expected status '%v', got '%v'", st, Cancelled)
	}
}

type playTest struct {
	ps playStagedTest

	dtmfChannel    chan ari.Event
	dtmfChannelSub *arimocks.Subscription
	player         *arimocks.Player
}

func (p *playTest) Setup() {
	p.ps.Setup()

	p.dtmfChannel = make(chan ari.Event)
	p.dtmfChannelSub = &arimocks.Subscription{}
	p.dtmfChannelSub.On("Events").Return((<-chan ari.Event)(p.dtmfChannel))
	p.dtmfChannelSub.On("Cancel").Return(nil)

	p.player = &arimocks.Player{}
	p.player.On("Subscribe", ari.Events.ChannelDtmfReceived).Return(p.dtmfChannelSub)
	p.player.On("StagePlay", mock.Anything, "sound:1").Return(p.ps.handle, nil)
}

func TestPlay(t *testing.T) {
	t.Run("testPlayNoURI", testPlayNoURI)
	t.Run("testPlay", testPlay)
	t.Run("testPlayDtmf", testPlayDtmf)
}

func testPlayNoURI(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playTest
	p.Setup()

	res, err := Play(ctx, p.player).Result()
	if err == nil || err.Error() != "empty playback URI list" {
		t.Errorf("Expected error '%v', got '%v'", "empty playback URI list", err)
	}
	if res.DTMF != "" {
		t.Errorf("Unexpected DTMF: %s", res.DTMF)
	}
}

func testPlay(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playTest
	p.Setup()

	go func() {
		p.ps.playbackStartedChan <- &ari.PlaybackStarted{}
		<-time.After(200 * time.Millisecond)
		p.ps.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	res, err := Play(ctx, p.player, URI("sound:1")).Result()
	if err != nil {
		t.Errorf("Unexpected error '%v'", err)
	}
	if res.Status != Finished {
		t.Errorf("Expected status '%v', got '%v'", Finished, res.Status)
	}
	if res.DTMF != "" {
		t.Errorf("Unexpected DTMF: %s", res.DTMF)
	}
}

func testPlayDtmf(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var p playTest
	p.Setup()

	go func() {
		p.ps.playbackStartedChan <- &ari.PlaybackStarted{}
		<-time.After(200 * time.Millisecond)

		p.dtmfChannel <- &ari.ChannelDtmfReceived{
			Digit: "1",
		}
		<-time.After(200 * time.Millisecond)

		p.ps.playbackEndChan <- &ari.PlaybackFinished{}
	}()

	res, err := Prompt(ctx, p.player, URI("sound:1")).Result()
	if err != nil {
		t.Error("Unexpected error", err)
	}

	if res.MatchResult != Complete {
		t.Errorf("Expected MatchResult '%v', got '%v'", Complete, res.MatchResult)
	}
	if res.DTMF != "1" {
		t.Errorf("Expected DTMF %s, got DTMF %s", "1", res.DTMF)
	}
}
<|endoftext|>


<commit_before>/*
 Copyright 2019 Padduck, LLC
  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 config

type Base struct {
	Auth     Auth     `json:"auth"`
	Listener Listener `json:"listen"`
	Console  Console  `json:"console"`
	Data     Data     `json:"data"`
}

type Data struct {
	ServerFolder   string `json:"servers"`
	TemplateFolder string `json:"templates"`
	CacheFolder    string `json:"cache"`
	ModuleFolder   string `json:"modules"`
	CrashLimit     int    `json:"crashLimit"`
	BasePath       string `json:"base"`
	LogFolder      string `json:"logs"`
}

type Console struct {
	Buffer  int  `json:"buffer"`
	Forward bool `json:"forward"`
}

type Auth struct {
	AuthURL      string
	InfoURL      string
	ClientID     string
	ClientSecret string
}

type Listener struct {
	Web     string `json:"web"`
	SFTP    string `json:"sftp"`
	SFTPKey string `json:"serverKey"`
}
<commit_msg>Update to use right path for config<commit_after>/*
 Copyright 2019 Padduck, LLC
  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 config

type Base struct {
	Auth     Auth     `json:"auth"`
	Listener Listener `json:"listen"`
	Console  Console  `json:"console"`
	Data     Data     `json:"data"`
}

type Data struct {
	ServerFolder   string `json:"servers"`
	TemplateFolder string `json:"templates"`
	CacheFolder    string `json:"cache"`
	ModuleFolder   string `json:"modules"`
	CrashLimit     int    `json:"crashLimit"`
	BasePath       string `json:"base"`
	LogFolder      string `json:"logs"`
}

type Console struct {
	Buffer  int  `json:"buffer"`
	Forward bool `json:"forward"`
}

type Auth struct {
	AuthURL      string `json:"authUrl"`
	InfoURL      string `json:"infoUrl"`
	ClientID     string `json:"clientId"`
	ClientSecret string `json:"clientSecret"`
}

type Listener struct {
	Web     string `json:"web"`
	SFTP    string `json:"sftp"`
	SFTPKey string `json:"serverKey"`
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"github.com/github/hub/utils"
	"os"
	"path/filepath"
	"strings"
)

var cmdAlias = &Command{
	Run:   alias,
	Usage: "alias [-s] [SHELL]",
	Short: "Show shell instructions for wrapping git",
	Long: `Shows shell instructions for wrapping git. If given, SHELL specifies the
type of shell; otherwise defaults to the value of SHELL environment
variable. With -s, outputs shell script suitable for eval.
`,
}

var flagAliasScript bool

func init() {
	cmdAlias.Flag.BoolVarP(&flagAliasScript, "script", "s", false, "SCRIPT")
	CmdRunner.Use(cmdAlias)
}

func alias(command *Command, args *Args) {
	var shell string
	if args.ParamsSize() > 0 {
		shell = args.FirstParam()
	} else {
		shell = os.Getenv("SHELL")
	}

	if shell == "" {
		utils.Check(fmt.Errorf("Unknown shell"))
	}

	shells := []string{"bash", "zsh", "sh", "ksh", "csh", "tcsh", "fish"}
	shell = filepath.Base(shell)
	var validShell bool
	for _, s := range shells {
		if s == shell {
			validShell = true
			break
		}
	}

	if !validShell {
		err := fmt.Errorf("hub alias: unsupported shell\nsupported shells: %s", strings.Join(shells, " "))
		utils.Check(err)
	}

	if flagAliasScript {
		var alias string
		switch shell {
		case "csh", "tcsh":
			alias = "alias git hub"
		default:
			alias = "alias git=hub"
		}

		fmt.Println(alias)
	} else {
		var profile string
		switch shell {
		case "bash":
			profile = "~/.bash_profile"
		case "zsh":
			profile = "~/.zshrc"
		case "ksh":
			profile = "~/.profile"
		case "fish":
			profile = "~/.config/fish/config.fish"
		case "tcsh":
			profile = "~/.tcshrc"
		default:
			profile = "your profile"
		}

		msg := fmt.Sprintf("# Wrap git automatically by adding the following to %s:\n", profile)
		fmt.Println(msg)

		var eval string
		switch shell {
		case "fish":
			eval = `eval (hub alias -s)`
		case "csh", "tcsh":
			eval = "eval \"`hub alias -s`\""
		default:
			eval = `eval "$(hub alias -s)"`
		}
		fmt.Println(eval)
	}

	os.Exit(0)
}
<commit_msg>Add csh => ~/.cshrc to profiles switch.<commit_after>package commands

import (
	"fmt"
	"github.com/github/hub/utils"
	"os"
	"path/filepath"
	"strings"
)

var cmdAlias = &Command{
	Run:   alias,
	Usage: "alias [-s] [SHELL]",
	Short: "Show shell instructions for wrapping git",
	Long: `Shows shell instructions for wrapping git. If given, SHELL specifies the
type of shell; otherwise defaults to the value of SHELL environment
variable. With -s, outputs shell script suitable for eval.
`,
}

var flagAliasScript bool

func init() {
	cmdAlias.Flag.BoolVarP(&flagAliasScript, "script", "s", false, "SCRIPT")
	CmdRunner.Use(cmdAlias)
}

func alias(command *Command, args *Args) {
	var shell string
	if args.ParamsSize() > 0 {
		shell = args.FirstParam()
	} else {
		shell = os.Getenv("SHELL")
	}

	if shell == "" {
		utils.Check(fmt.Errorf("Unknown shell"))
	}

	shells := []string{"bash", "zsh", "sh", "ksh", "csh", "tcsh", "fish"}
	shell = filepath.Base(shell)
	var validShell bool
	for _, s := range shells {
		if s == shell {
			validShell = true
			break
		}
	}

	if !validShell {
		err := fmt.Errorf("hub alias: unsupported shell\nsupported shells: %s", strings.Join(shells, " "))
		utils.Check(err)
	}

	if flagAliasScript {
		var alias string
		switch shell {
		case "csh", "tcsh":
			alias = "alias git hub"
		default:
			alias = "alias git=hub"
		}

		fmt.Println(alias)
	} else {
		var profile string
		switch shell {
		case "bash":
			profile = "~/.bash_profile"
		case "zsh":
			profile = "~/.zshrc"
		case "ksh":
			profile = "~/.profile"
		case "fish":
			profile = "~/.config/fish/config.fish"
		case "csh":
			profile = "~/.cshrc"
		case "tcsh":
			profile = "~/.tcshrc"
		default:
			profile = "your profile"
		}

		msg := fmt.Sprintf("# Wrap git automatically by adding the following to %s:\n", profile)
		fmt.Println(msg)

		var eval string
		switch shell {
		case "fish":
			eval = `eval (hub alias -s)`
		case "csh", "tcsh":
			eval = "eval \"`hub alias -s`\""
		default:
			eval = `eval "$(hub alias -s)"`
		}
		fmt.Println(eval)
	}

	os.Exit(0)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ApplicationSpec defines the specification for an Application.
type ApplicationSpec struct {
	// Type is the type of the application (e.g. WordPress, MySQL, Cassandra).
	Type string `json:"type,omitempty"`

	// ComponentGroupKinds is a list of Kinds for Application's components (e.g. Deployments, Pods, Services, CRDs). It
	// can be used in conjunction with the Application's Selector to list or watch the Applications components.
	ComponentGroupKinds []metav1.GroupKind `json:"componentKinds,omitempty"`

	// Selector is a label query over kinds that created by the application. It must match the component objects' labels.
	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
	Selector *metav1.LabelSelector `json:"selector,omitempty"`

	// Version is an optional version indicator for the Application.
	Version string `json:"version,omitempty"`

	// Description is a brief string description of the Application.
	Description string `json:"description,omitempty"`

	// Maintainers is an optional list of maintainers of the application. The maintainers in this list maintain the
	// the source code, images, and package for the application.
	Maintainers []Maintainer `json:"maintainers,omitempty"`

	// Owners is an optional list of the owners of the installed application. The owners of the application should be
	// contacted in the event of a planned or unplanned disruption affecting the application.
	Owners []string `json:"owners,omitempty"`

	// Keywords is an optional list of key words associated with the application (e.g. MySQL, RDBMS, database).
	Keywords []string `json:"keywords,omitempty"`

	// Info contains human readable key,value pairs for the Application.
	Info []InfoItem `json:"info,omitempty"`

	// Links are a list of descriptive URLs intended to be used to surface additional documentation, dashboards, etc.
	Links []Link `json:"links,omitempty"`

	// Notes contain a human readable snippets intended as a quick start for the users of the Application.
	Notes string `json:"notes,omitempty"`

	// AssemblyPhase represents the current phase of the application's assembly.
	AssemblyPhase ApplicationAssemblyPhase `json:"assemblyPhase,omitempty"`
}

// ApplicationStatus defines controllers the observed state of Application
type ApplicationStatus struct {
	// ObservedGeneration is used by the Application Controller to report the last Generation of an Application
	// that it has observed.
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

// Maintainer contains information about an individual or organization that maintains the source code, images, and
// package for an Application. An Application can have more than one maintainer.
type Maintainer struct {
	// Name is the descriptive name of the maintainer.
	Name string `json:"name,omitempty"`

	// Url could typically be a website address.
	Url string `json:"url,omitempty"`

	// Email is the email address.
	Email string `json:"email,omitempty"`
}

// Link contains information about an URL to surface documentation, dashboards, etc.
type Link struct {
	// Description is human readable content explaining the purpose of the link.
	Description string `json:"description,omitempty"`

	// Url typically points at a website address.
	Url string `json:"url,omitempty"`
}

// InfoItem is a human readable key,value pair containing important information about how to access the Application.
type InfoItem struct {
	// Name is a human readable title for this piece of information.
	Name string `json:"name,omitempty"`

	// Value is human readable content.
	Value string `json:"value,omitempty"`
}

type ApplicationAssemblyPhase string

const (
	// Used to indicate that not all of application's components
	// have been deployed yet.
	Pending ApplicationAssemblyPhase = "Pending"
	// Used to indicate that all of application's components
	// have alraedy been deployed.
	Succeeded = "Succeeded"
	// Used to indicate that deployment of application's components
	// failed. Some components might be present, but deployment of
	// the remaining ones will not be re-attempted.
	Failed = "Failed"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Application
// +k8s:openapi-gen=true
// +resource:path=applications
// The Application object acts as an aggregator for components that comprise an Application. Its
// Spec.ComponentGroupKinds indicate the GroupKinds of the components the comprise the Application. Its Spec. Selector
// is used to list and watch those components. All components of an Application should be labeled such the Application's
// Spec. Selector matches.
type Application struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	// The specification object for the Application.
	Spec ApplicationSpec `json:"spec,omitempty"`
	// The status object for the Application.
	Status ApplicationStatus `json:"status,omitempty"`
}
<commit_msg>Added a note about the meaning of empty assembly phase<commit_after>/*
Copyright 2018 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 v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ApplicationSpec defines the specification for an Application.
type ApplicationSpec struct {
	// Type is the type of the application (e.g. WordPress, MySQL, Cassandra).
	Type string `json:"type,omitempty"`

	// ComponentGroupKinds is a list of Kinds for Application's components (e.g. Deployments, Pods, Services, CRDs). It
	// can be used in conjunction with the Application's Selector to list or watch the Applications components.
	ComponentGroupKinds []metav1.GroupKind `json:"componentKinds,omitempty"`

	// Selector is a label query over kinds that created by the application. It must match the component objects' labels.
	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
	Selector *metav1.LabelSelector `json:"selector,omitempty"`

	// Version is an optional version indicator for the Application.
	Version string `json:"version,omitempty"`

	// Description is a brief string description of the Application.
	Description string `json:"description,omitempty"`

	// Maintainers is an optional list of maintainers of the application. The maintainers in this list maintain the
	// the source code, images, and package for the application.
	Maintainers []Maintainer `json:"maintainers,omitempty"`

	// Owners is an optional list of the owners of the installed application. The owners of the application should be
	// contacted in the event of a planned or unplanned disruption affecting the application.
	Owners []string `json:"owners,omitempty"`

	// Keywords is an optional list of key words associated with the application (e.g. MySQL, RDBMS, database).
	Keywords []string `json:"keywords,omitempty"`

	// Info contains human readable key,value pairs for the Application.
	Info []InfoItem `json:"info,omitempty"`

	// Links are a list of descriptive URLs intended to be used to surface additional documentation, dashboards, etc.
	Links []Link `json:"links,omitempty"`

	// Notes contain a human readable snippets intended as a quick start for the users of the Application.
	Notes string `json:"notes,omitempty"`

	// AssemblyPhase represents the current phase of the application's assembly.
	// An empty value is equivalent to "Succeeded".
	AssemblyPhase ApplicationAssemblyPhase `json:"assemblyPhase,omitempty"`
}

// ApplicationStatus defines controllers the observed state of Application
type ApplicationStatus struct {
	// ObservedGeneration is used by the Application Controller to report the last Generation of an Application
	// that it has observed.
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

// Maintainer contains information about an individual or organization that maintains the source code, images, and
// package for an Application. An Application can have more than one maintainer.
type Maintainer struct {
	// Name is the descriptive name of the maintainer.
	Name string `json:"name,omitempty"`

	// Url could typically be a website address.
	Url string `json:"url,omitempty"`

	// Email is the email address.
	Email string `json:"email,omitempty"`
}

// Link contains information about an URL to surface documentation, dashboards, etc.
type Link struct {
	// Description is human readable content explaining the purpose of the link.
	Description string `json:"description,omitempty"`

	// Url typically points at a website address.
	Url string `json:"url,omitempty"`
}

// InfoItem is a human readable key,value pair containing important information about how to access the Application.
type InfoItem struct {
	// Name is a human readable title for this piece of information.
	Name string `json:"name,omitempty"`

	// Value is human readable content.
	Value string `json:"value,omitempty"`
}

type ApplicationAssemblyPhase string

const (
	// Used to indicate that not all of application's components
	// have been deployed yet.
	Pending ApplicationAssemblyPhase = "Pending"
	// Used to indicate that all of application's components
	// have alraedy been deployed.
	Succeeded = "Succeeded"
	// Used to indicate that deployment of application's components
	// failed. Some components might be present, but deployment of
	// the remaining ones will not be re-attempted.
	Failed = "Failed"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Application
// +k8s:openapi-gen=true
// +resource:path=applications
// The Application object acts as an aggregator for components that comprise an Application. Its
// Spec.ComponentGroupKinds indicate the GroupKinds of the components the comprise the Application. Its Spec. Selector
// is used to list and watch those components. All components of an Application should be labeled such the Application's
// Spec. Selector matches.
type Application struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	// The specification object for the Application.
	Spec ApplicationSpec `json:"spec,omitempty"`
	// The status object for the Application.
	Status ApplicationStatus `json:"status,omitempty"`
}
<|endoftext|>