<commit_before>package goapns

import "fmt"

type Payload struct {
	Badge            int
	Sound            string
	ContentAvailable int
	Category         string
}

func NewPayload() Payload {
	p := Payload{-1, "", 0, ""}
	fmt.Printf("INITING, Badge: %v\n", p.Badge)
	return p
}

func (p *Payload) MapInto(mapped map[string]interface{}) map[string]interface{} {
	fmt.Printf("JSONING, Badge: %v\n", p.Badge)
	if p.Badge >= 0 {
		//Only set badge if the user specified so (by setting a >= 0 value).
		//If not, the badge is unchanged in the app.
		mapped["badge"] = p.Badge
	}
	if p.Sound != "" {
		mapped["sound"] = p.Sound
	}
	if p.ContentAvailable != 0 {
		mapped["content-available"] = 1
	}
	if p.Category != "" {
		mapped["category"] = p.Category
	}
	return mapped
}
<commit_msg>Documented Payload.go<commit_after>package goapns

//Payload defines properties that are described as in Apples documentation.
//Badge, Sound, ContentAvailable and Category are those.
//Think of Payload as a meta-object to your notification as it specify the behaviour
//but not the actual alert.
type Payload struct {
	//Badge is the number to display as the badge of the app icon.
	//If this property is absent, the badge is not changed.
	//To remove the badge, set the value of this property to 0.
	Badge int

	//Sound specified tha name of a sound file in the app bundle or in the Library/Sounds folder of the app’s data container.
	//The sound in this file is played as an alert.
	//If the sound file doesn’t exist or default is specified as the value, the default alert sound is played.
	//The audio must be in one of the audio data formats that are compatible with system sounds.
	Sound string

	//ContentAvailable: if this key is provided with a value of 1 to indicate that new content is available.
	//Including this key and value means that when your app is launched in the background or resumed,
	//application:didReceiveRemoteNotification:fetchCompletionHandler: is called.
	ContentAvailable int

	// Category: provide this key with a string value that represents the identifier property of the UIMutableUserNotificationCategory object you created to define custom actions.
	// To learn more about using custom actions, see Registering Your Actionable Notification Types.
	Category string
}

//NewPayload provides a initializer of Payload with empty values and no badge.
func NewPayload() Payload {
	p := Payload{-1, "", 0, ""}
	return p
}

//MapInto is passed in a map on which the Payload content is appended to.
//It return a new map with every property and key set, ready to build a JSON from it.
func (p *Payload) MapInto(mapped map[string]interface{}) map[string]interface{} {
	if p.Badge >= 0 {
		//Only set badge if the user specified so (by setting a >= 0 value).
		//If not, Badge is ommitted in JSON
		//and therefore the badge on the app is unchanged
		mapped["badge"] = p.Badge
	}
	if p.Sound != "" {
		mapped["sound"] = p.Sound
	}
	if p.ContentAvailable != 0 {
		mapped["content-available"] = 1
	}
	if p.Category != "" {
		mapped["category"] = p.Category
	}
	return mapped
}
<|endoftext|>


<commit_before>package user_ctrl

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gocms-io/gocms/context"
	"github.com/gocms-io/gocms/models"
	"github.com/gocms-io/gocms/utility"
	"github.com/gocms-io/gocms/utility/errors"
	"net/http"
)

/**
* @api {post} /user/email/activate Request New Email Activation Link
* @apiName ActivateEmail
* @apiGroup User
* @apiUse RequestEmailActivationLink
* @apiDescription Request a new activation link for a registered email.
 */
func (uc *UserController) requestActivationLink(c *gin.Context) {

	// get email
	var requestEmailActivationLinkInput models.RequestEmailActivationLinkInput
	err := c.BindJSON(&requestEmailActivationLinkInput)
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
	}
	err = uc.ServicesGroup.EmailService.SendEmailActivationCode(requestEmailActivationLinkInput.Email)
	if err != nil {
		errors.Response(c, http.StatusBadRequest, "Error sending activation code.", err)
	}

}

/**
* @api {get} /user/email/activate Activate Email
* @apiName ActivateEmail
* @apiGroup User
* @apiDescription This endpoint requires two url params &email and &code. Links are auto generated for this endpoint by the system. This will likely never be called diructly from an app.
 */
func (uc *UserController) activateEmail(c *gin.Context) {
	code := c.Query("code")
	if code == "" {
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error", context.Config.RedirectRootUrl))
		return
	}

	email := c.Query("email")
	if email == "" {

		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error", context.Config.RedirectRootUrl))
		return
	}

	// get user
	user, err := uc.ServicesGroup.UserService.GetByEmail(email)
	if err != nil {
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error", context.Config.RedirectRootUrl))
		return
	}

	if ok := uc.ServicesGroup.EmailService.VerifyEmailActivationCode(user.Id, code); !ok {
		err = errors.New(errors.ApiError_Activating_Email)
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error", context.Config.RedirectRootUrl))
		return
	}

	// set email to verified
	err = uc.ServicesGroup.EmailService.SetVerified(email)
	if err != nil {
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error", context.Config.RedirectRootUrl))
		return
	}

	c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/success", context.Config.RedirectRootUrl))
}

/**
* @api {post} /user/email Add Email
* @apiName AddEmail
* @apiGroup User
*
* @apiUse AuthHeader
* @apiUse EmailInput
* @apiUse EmailDisplay
* @apiPermission Authenticated
 */
func (uc *UserController) addEmail(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get reqeust data
	var addEmailInput models.EmailInput
	err := c.BindJSON(&addEmailInput) // update any changes from request
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
		return
	}

	// verify password
	if ok := uc.ServicesGroup.AuthService.VerifyPassword(authUser.Password, addEmailInput.Password); !ok {
		errors.Response(c, http.StatusUnauthorized, "Bad Password.", err)
		return
	}

	// convert input to model
	emailToAdd := models.Email{
		Email:      addEmailInput.Email,
		IsVerified: false,
		IsPrimary:  false,
		UserId:     authUser.Id,
	}

	// add email
	err = uc.ServicesGroup.EmailService.AddEmail(&emailToAdd)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't add email to user.", err)
		return
	}

	// send verification email
	err = uc.ServicesGroup.EmailService.SendEmailActivationCode(emailToAdd.Email)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't add email to user.", err)
		return
	}

	// create email display and send
	emailDisplay := models.EmailDisplay{
		Email:     emailToAdd.Email,
		Id:        emailToAdd.Id,
		IsPrimary: emailToAdd.IsPrimary,
		Verified:  emailToAdd.IsVerified,
	}

	c.JSON(http.StatusOK, emailDisplay)
}

/**
* @api {put} /user/email/promote Promote Email
* @apiName PromoteEmail
* @apiGroup User
* @apiUse EmailInput
* @apiUse AuthHeader
* @apiPermission Authenticated
 */
func (uc *UserController) promoteEmail(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get reqeust data
	var promoteEmailInput models.EmailInput
	err := c.BindJSON(&promoteEmailInput) // update any changes from request
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
		return
	}

	// verify password
	if ok := uc.ServicesGroup.AuthService.VerifyPassword(authUser.Password, promoteEmailInput.Password); !ok {
		errors.Response(c, http.StatusUnauthorized, "Bad Password.", err)
		return
	}

	email := models.Email{
		Email:  promoteEmailInput.Email,
		UserId: authUser.Id,
	}

	// promote email
	err = uc.ServicesGroup.EmailService.PromoteEmail(&email)
	if err != nil {
		errors.Response(c, http.StatusBadRequest, "Error promoting email.", err)
		return
	}

	c.Status(http.StatusOK)
}

/**
* @api {delete} /user/email Delete Email
* @apiName DeleteEmail
* @apiGroup User
*
* @apiUse AuthHeader
* @apiUse EmailInput
* @apiPermission Authenticated
 */
func (uc *UserController) deleteEmail(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get reqeust data
	var addEmailInput models.EmailInput
	err := c.BindJSON(&addEmailInput) // update any changes from request
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
		return
	}

	// verify password
	if ok := uc.ServicesGroup.AuthService.VerifyPassword(authUser.Password, addEmailInput.Password); !ok {
		errors.Response(c, http.StatusUnauthorized, "Bad Password.", err)
		return
	}

	// convert input to model
	emailToDelete := models.Email{
		Email:  addEmailInput.Email,
		UserId: authUser.Id,
	}

	// add email
	err = uc.ServicesGroup.EmailService.DeleteEmail(&emailToDelete)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't delete email.", err)
		return
	}

	c.Status(http.StatusOK)
}

/**
* @api {get} /user/email Get Emails
* @apiName GetEmails
* @apiGroup User
*
* @apiUse AuthHeader
* @apiUse EmailDisplay
* @apiPermission Authenticated
 */
func (uc *UserController) getEmails(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get all emails
	emails, err := uc.ServicesGroup.EmailService.GetEmailsByUserId(authUser.Id)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't add email to user.", err)
		return
	}

	emailDisplays := make([]*models.EmailDisplay, len(emails))

	for i, ed := range emails {
		emailDisplays[i] = ed.GetEmailDisplay()
	}

	c.JSON(http.StatusOK, emailDisplays)
}
<commit_msg>updated to point to html<commit_after>package user_ctrl

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gocms-io/gocms/context"
	"github.com/gocms-io/gocms/models"
	"github.com/gocms-io/gocms/utility"
	"github.com/gocms-io/gocms/utility/errors"
	"net/http"
)

/**
* @api {post} /user/email/activate Request New Email Activation Link
* @apiName ActivateEmail
* @apiGroup User
* @apiUse RequestEmailActivationLink
* @apiDescription Request a new activation link for a registered email.
 */
func (uc *UserController) requestActivationLink(c *gin.Context) {

	// get email
	var requestEmailActivationLinkInput models.RequestEmailActivationLinkInput
	err := c.BindJSON(&requestEmailActivationLinkInput)
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
	}
	err = uc.ServicesGroup.EmailService.SendEmailActivationCode(requestEmailActivationLinkInput.Email)
	if err != nil {
		errors.Response(c, http.StatusBadRequest, "Error sending activation code.", err)
	}

}

/**
* @api {get} /user/email/activate Activate Email
* @apiName ActivateEmail
* @apiGroup User
* @apiDescription This endpoint requires two url params &email and &code. Links are auto generated for this endpoint by the system. This will likely never be called diructly from an app.
 */
func (uc *UserController) activateEmail(c *gin.Context) {
	code := c.Query("code")
	if code == "" {
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error.html", context.Config.RedirectRootUrl))
		return
	}

	email := c.Query("email")
	if email == "" {

		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error.html", context.Config.RedirectRootUrl))
		return
	}

	// get user
	user, err := uc.ServicesGroup.UserService.GetByEmail(email)
	if err != nil {
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error.html", context.Config.RedirectRootUrl))
		return
	}

	if ok := uc.ServicesGroup.EmailService.VerifyEmailActivationCode(user.Id, code); !ok {
		err = errors.New(errors.ApiError_Activating_Email)
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error.html", context.Config.RedirectRootUrl))
		return
	}

	// set email to verified
	err = uc.ServicesGroup.EmailService.SetVerified(email)
	if err != nil {
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/error.html", context.Config.RedirectRootUrl))
		return
	}

	c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%v/activateEmail/success.html", context.Config.RedirectRootUrl))
}

/**
* @api {post} /user/email Add Email
* @apiName AddEmail
* @apiGroup User
*
* @apiUse AuthHeader
* @apiUse EmailInput
* @apiUse EmailDisplay
* @apiPermission Authenticated
 */
func (uc *UserController) addEmail(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get reqeust data
	var addEmailInput models.EmailInput
	err := c.BindJSON(&addEmailInput) // update any changes from request
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
		return
	}

	// verify password
	if ok := uc.ServicesGroup.AuthService.VerifyPassword(authUser.Password, addEmailInput.Password); !ok {
		errors.Response(c, http.StatusUnauthorized, "Bad Password.", err)
		return
	}

	// convert input to model
	emailToAdd := models.Email{
		Email:      addEmailInput.Email,
		IsVerified: false,
		IsPrimary:  false,
		UserId:     authUser.Id,
	}

	// add email
	err = uc.ServicesGroup.EmailService.AddEmail(&emailToAdd)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't add email to user.", err)
		return
	}

	// send verification email
	err = uc.ServicesGroup.EmailService.SendEmailActivationCode(emailToAdd.Email)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't add email to user.", err)
		return
	}

	// create email display and send
	emailDisplay := models.EmailDisplay{
		Email:     emailToAdd.Email,
		Id:        emailToAdd.Id,
		IsPrimary: emailToAdd.IsPrimary,
		Verified:  emailToAdd.IsVerified,
	}

	c.JSON(http.StatusOK, emailDisplay)
}

/**
* @api {put} /user/email/promote Promote Email
* @apiName PromoteEmail
* @apiGroup User
* @apiUse EmailInput
* @apiUse AuthHeader
* @apiPermission Authenticated
 */
func (uc *UserController) promoteEmail(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get reqeust data
	var promoteEmailInput models.EmailInput
	err := c.BindJSON(&promoteEmailInput) // update any changes from request
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
		return
	}

	// verify password
	if ok := uc.ServicesGroup.AuthService.VerifyPassword(authUser.Password, promoteEmailInput.Password); !ok {
		errors.Response(c, http.StatusUnauthorized, "Bad Password.", err)
		return
	}

	email := models.Email{
		Email:  promoteEmailInput.Email,
		UserId: authUser.Id,
	}

	// promote email
	err = uc.ServicesGroup.EmailService.PromoteEmail(&email)
	if err != nil {
		errors.Response(c, http.StatusBadRequest, "Error promoting email.", err)
		return
	}

	c.Status(http.StatusOK)
}

/**
* @api {delete} /user/email Delete Email
* @apiName DeleteEmail
* @apiGroup User
*
* @apiUse AuthHeader
* @apiUse EmailInput
* @apiPermission Authenticated
 */
func (uc *UserController) deleteEmail(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get reqeust data
	var addEmailInput models.EmailInput
	err := c.BindJSON(&addEmailInput) // update any changes from request
	if err != nil {
		errors.Response(c, http.StatusBadRequest, err.Error(), err)
		return
	}

	// verify password
	if ok := uc.ServicesGroup.AuthService.VerifyPassword(authUser.Password, addEmailInput.Password); !ok {
		errors.Response(c, http.StatusUnauthorized, "Bad Password.", err)
		return
	}

	// convert input to model
	emailToDelete := models.Email{
		Email:  addEmailInput.Email,
		UserId: authUser.Id,
	}

	// add email
	err = uc.ServicesGroup.EmailService.DeleteEmail(&emailToDelete)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't delete email.", err)
		return
	}

	c.Status(http.StatusOK)
}

/**
* @api {get} /user/email Get Emails
* @apiName GetEmails
* @apiGroup User
*
* @apiUse AuthHeader
* @apiUse EmailDisplay
* @apiPermission Authenticated
 */
func (uc *UserController) getEmails(c *gin.Context) {

	// get logged in user
	authUser, _ := utility.GetUserFromContext(c)

	// get all emails
	emails, err := uc.ServicesGroup.EmailService.GetEmailsByUserId(authUser.Id)
	if err != nil {
		errors.Response(c, http.StatusInternalServerError, "Couldn't add email to user.", err)
		return
	}

	emailDisplays := make([]*models.EmailDisplay, len(emails))

	for i, ed := range emails {
		emailDisplays[i] = ed.GetEmailDisplay()
	}

	c.JSON(http.StatusOK, emailDisplays)
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcchain

import (
	"fmt"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
)

// maybeAcceptBlock potentially accepts a block into the memory block chain.
// It performs several validation checks which depend on its position within
// the block chain before adding it.  The block is expected to have already gone
// through ProcessBlock before calling this function with it.
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block) error {
	// Get a block node for the block previous to this one.  Will be nil
	// if this is the genesis block.
	prevNode, err := b.getPrevNodeFromBlock(block)
	if err != nil {
		log.Errorf("getPrevNodeFromBlock: %v", err)
		return err
	}

	// The height of this block one more than the referenced previous block.
	blockHeight := int64(0)
	if prevNode != nil {
		blockHeight = prevNode.height + 1
	}

	// Ensure the difficulty specified in the block header matches the
	// calculated difficulty based on the previous block and difficulty
	// retarget rules.
	blockHeader := block.MsgBlock().Header
	expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode, block)
	if err != nil {
		log.Errorf("calcNextRequiredDifficulty: %v", err)
		return err
	}
	blockDifficulty := blockHeader.Bits
	if blockDifficulty != expectedDifficulty {
		str := "block difficulty of %d is not the expected value of %d"
		str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
		return RuleError(str)
	}

	// Ensure the timestamp for the block header is after the median time of
	// the last several blocks (medianTimeBlocks).
	medianTime, err := b.calcPastMedianTime(prevNode)
	if err != nil {
		log.Errorf("calcPastMedianTime: %v", err)
		return err
	}
	if !blockHeader.Timestamp.After(medianTime) {
		str := "block timestamp of %v is not after expected %v"
		str = fmt.Sprintf(str, blockHeader.Timestamp, medianTime)
		return RuleError(str)
	}

	// Ensure all transactions in the block are finalized.
	for i, tx := range block.MsgBlock().Transactions {
		if !isFinalizedTransaction(tx, blockHeight, blockHeader.Timestamp) {
			// Use the TxSha function from the block rather
			// than the transaction itself since the block version
			// is cached.  Also, it's safe to ignore the error here
			// since the only reason TxSha can fail is if the index
			// is out of range which is impossible here.
			txSha, _ := block.TxSha(i)
			str := fmt.Sprintf("block contains unfinalized "+
				"transaction %v", txSha)
			return RuleError(str)
		}
	}

	// Ensure chain matches up to predetermined checkpoints.
	// It's safe to ignore the error on Sha since it's already cached.
	blockHash, _ := block.Sha()
	if !b.verifyCheckpoint(blockHeight, blockHash) {
		// TODO(davec): This should probably be a distinct error type
		// (maybe CheckpointError).  Since this error shouldn't happen
		// unless the peer is connected to a rogue network serving up an
		// alternate chain, the caller would likely need to react by
		// disconnecting peers and rolling back the chain to the last
		// known good point.
		str := fmt.Sprintf("block at height %d does not match "+
			"checkpoint hash", blockHeight)
		return RuleError(str)
	}

	// Reject version 1 blocks once a majority of the network has upgraded.
	// Rules:
	//  95% (950 / 1000) for main network
	//  75% (75 / 100) for the test network
	// This is part of BIP_0034.
	if blockHeader.Version == 1 {
		minRequired := uint64(950)
		numToCheck := uint64(1000)
		if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet {
			minRequired = 75
			numToCheck = 100
		}
		if b.isMajorityVersion(2, prevNode, minRequired, numToCheck) {
			str := "new blocks with version %d are no longer valid"
			str = fmt.Sprintf(str, blockHeader.Version)
			return RuleError(str)
		}
	}

	// Ensure coinbase starts with serialized block heights for blocks
	// whose version is the serializedHeightVersion or newer once a majority
	// of the network has upgraded.
	// Rules:
	//  75% (750 / 1000) for main network
	//  51% (51 / 100) for the test network
	// This is part of BIP_0034.
	if blockHeader.Version >= serializedHeightVersion {
		minRequired := uint64(750)
		numToCheck := uint64(1000)
		if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet {
			minRequired = 51
			numToCheck = 100
		}
		if b.isMajorityVersion(serializedHeightVersion, prevNode,
			minRequired, numToCheck) {

			expectedHeight := int64(0)
			if prevNode != nil {
				expectedHeight = prevNode.height + 1
			}
			coinbaseTx := block.MsgBlock().Transactions[0]
			err := checkSerializedHeight(coinbaseTx, expectedHeight)
			if err != nil {
				return err
			}
		}
	}

	// Create a new block node for the block and add it to the in-memory
	// block chain (could be either a side chain or the main chain).
	newNode := newBlockNode(block)
	if prevNode != nil {
		newNode.parent = prevNode
		newNode.height = blockHeight
		newNode.workSum.Add(prevNode.workSum, newNode.workSum)
	}

	// Connect the passed block to the chain while respecting proper chain
	// selection according to the chain with the most proof of work.  This
	// also handles validation of the transaction scripts.
	err = b.connectBestChain(newNode, block)
	if err != nil {
		log.Errorf("connectBestChain: %v", err)
		return err
	}

	// Notify the caller that the new block was accepted into the block
	// chain.  The caller would typically want to react by relaying the
	// inventory to other peers.
	b.sendNotification(NTBlockAccepted, block)

	return nil
}
<commit_msg>Ensure height is set on block as well as the node.<commit_after>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcchain

import (
	"fmt"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
)

// maybeAcceptBlock potentially accepts a block into the memory block chain.
// It performs several validation checks which depend on its position within
// the block chain before adding it.  The block is expected to have already gone
// through ProcessBlock before calling this function with it.
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block) error {
	// Get a block node for the block previous to this one.  Will be nil
	// if this is the genesis block.
	prevNode, err := b.getPrevNodeFromBlock(block)
	if err != nil {
		log.Errorf("getPrevNodeFromBlock: %v", err)
		return err
	}

	// The height of this block one more than the referenced previous block.
	blockHeight := int64(0)
	if prevNode != nil {
		blockHeight = prevNode.height + 1
	}
	block.SetHeight(blockHeight)

	// Ensure the difficulty specified in the block header matches the
	// calculated difficulty based on the previous block and difficulty
	// retarget rules.
	blockHeader := block.MsgBlock().Header
	expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode, block)
	if err != nil {
		log.Errorf("calcNextRequiredDifficulty: %v", err)
		return err
	}
	blockDifficulty := blockHeader.Bits
	if blockDifficulty != expectedDifficulty {
		str := "block difficulty of %d is not the expected value of %d"
		str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
		return RuleError(str)
	}

	// Ensure the timestamp for the block header is after the median time of
	// the last several blocks (medianTimeBlocks).
	medianTime, err := b.calcPastMedianTime(prevNode)
	if err != nil {
		log.Errorf("calcPastMedianTime: %v", err)
		return err
	}
	if !blockHeader.Timestamp.After(medianTime) {
		str := "block timestamp of %v is not after expected %v"
		str = fmt.Sprintf(str, blockHeader.Timestamp, medianTime)
		return RuleError(str)
	}

	// Ensure all transactions in the block are finalized.
	for i, tx := range block.MsgBlock().Transactions {
		if !isFinalizedTransaction(tx, blockHeight, blockHeader.Timestamp) {
			// Use the TxSha function from the block rather
			// than the transaction itself since the block version
			// is cached.  Also, it's safe to ignore the error here
			// since the only reason TxSha can fail is if the index
			// is out of range which is impossible here.
			txSha, _ := block.TxSha(i)
			str := fmt.Sprintf("block contains unfinalized "+
				"transaction %v", txSha)
			return RuleError(str)
		}
	}

	// Ensure chain matches up to predetermined checkpoints.
	// It's safe to ignore the error on Sha since it's already cached.
	blockHash, _ := block.Sha()
	if !b.verifyCheckpoint(blockHeight, blockHash) {
		// TODO(davec): This should probably be a distinct error type
		// (maybe CheckpointError).  Since this error shouldn't happen
		// unless the peer is connected to a rogue network serving up an
		// alternate chain, the caller would likely need to react by
		// disconnecting peers and rolling back the chain to the last
		// known good point.
		str := fmt.Sprintf("block at height %d does not match "+
			"checkpoint hash", blockHeight)
		return RuleError(str)
	}

	// Reject version 1 blocks once a majority of the network has upgraded.
	// Rules:
	//  95% (950 / 1000) for main network
	//  75% (75 / 100) for the test network
	// This is part of BIP_0034.
	if blockHeader.Version == 1 {
		minRequired := uint64(950)
		numToCheck := uint64(1000)
		if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet {
			minRequired = 75
			numToCheck = 100
		}
		if b.isMajorityVersion(2, prevNode, minRequired, numToCheck) {
			str := "new blocks with version %d are no longer valid"
			str = fmt.Sprintf(str, blockHeader.Version)
			return RuleError(str)
		}
	}

	// Ensure coinbase starts with serialized block heights for blocks
	// whose version is the serializedHeightVersion or newer once a majority
	// of the network has upgraded.
	// Rules:
	//  75% (750 / 1000) for main network
	//  51% (51 / 100) for the test network
	// This is part of BIP_0034.
	if blockHeader.Version >= serializedHeightVersion {
		minRequired := uint64(750)
		numToCheck := uint64(1000)
		if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet {
			minRequired = 51
			numToCheck = 100
		}
		if b.isMajorityVersion(serializedHeightVersion, prevNode,
			minRequired, numToCheck) {

			expectedHeight := int64(0)
			if prevNode != nil {
				expectedHeight = prevNode.height + 1
			}
			coinbaseTx := block.MsgBlock().Transactions[0]
			err := checkSerializedHeight(coinbaseTx, expectedHeight)
			if err != nil {
				return err
			}
		}
	}

	// Create a new block node for the block and add it to the in-memory
	// block chain (could be either a side chain or the main chain).
	newNode := newBlockNode(block)
	if prevNode != nil {
		newNode.parent = prevNode
		newNode.height = blockHeight
		newNode.workSum.Add(prevNode.workSum, newNode.workSum)
	}

	// Connect the passed block to the chain while respecting proper chain
	// selection according to the chain with the most proof of work.  This
	// also handles validation of the transaction scripts.
	err = b.connectBestChain(newNode, block)
	if err != nil {
		log.Errorf("connectBestChain: %v", err)
		return err
	}

	// Notify the caller that the new block was accepted into the block
	// chain.  The caller would typically want to react by relaying the
	// inventory to other peers.
	b.sendNotification(NTBlockAccepted, block)

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 The appc 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 systemdnspawn

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"
	"syscall"

	"github.com/appc/spec/schema/types"
)

var pathlist = []string{"/usr/local/sbin", "/usr/local/bin", "/usr/sbin",
	"/usr/bin", "/sbin", "/bin"}

type Engine struct{}

func (e Engine) Run(command string, args []string, environment types.Environment, chroot, workingDir string) error {
	nspawncmd := []string{"systemd-nspawn", "-D", chroot}

	systemdVersion, err := getSystemdVersion()
	if err != nil {
		return err
	}

	if systemdVersion >= 209 {
		nspawncmd = append(nspawncmd, "--quiet", "--register=no")
	}
	if workingDir != "" {
		if systemdVersion < 229 {
			return fmt.Errorf("the working dir can only be set on systems with systemd-nspawn >= 229")
		}
		nspawncmd = append(nspawncmd, "--chdir", workingDir)
	}

	for _, envVar := range environment {
		nspawncmd = append(nspawncmd, "--setenv", envVar.Name+"="+envVar.Value)
	}

	nspawncmd = append(nspawncmd, "--setenv", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")

	abscmd, err := findCmdInPath(pathlist, command, chroot)
	if err != nil {
		return err
	}

	finfo, err := os.Lstat(path.Join(chroot, abscmd))
	switch {
	case os.IsNotExist(err):
		return fmt.Errorf("binary %q doesn't exist", abscmd)
	case err != nil:
		return err
	}

	if finfo.Mode()&os.ModeSymlink != 0 && systemdVersion < 228 {
		fmt.Fprintf(os.Stderr, "Warning: %q is a symlink, which systemd-nspawn version %d might error on\n", abscmd, systemdVersion)
	}

	nspawncmd = append(nspawncmd, abscmd)
	nspawncmd = append(nspawncmd, args...)

	execCmd := exec.Command(nspawncmd[0], nspawncmd[1:]...)
	execCmd.Stdin = os.Stdin
	execCmd.Stdout = os.Stdout
	execCmd.Stderr = os.Stderr
	execCmd.Env = []string{"SYSTEMD_LOG_LEVEL=err"}

	err = execCmd.Run()
	if err == exec.ErrNotFound {
		return fmt.Errorf("systemd-nspawn is required but not found")
	}
	if err == nil {
		return nil
	}
	if exitErr, ok := err.(*exec.ExitError); ok {
		code := exitErr.Sys().(syscall.WaitStatus).ExitStatus()
		return fmt.Errorf("non-zero exit code: %d", code)
	}
	return err
}

func getSystemdVersion() (int, error) {
	_, err := exec.LookPath("systemctl")
	if err == exec.ErrNotFound {
		return 0, fmt.Errorf("system does not have systemd")
	}

	blob, err := exec.Command("systemctl", "--version").Output()
	if err != nil {
		return 0, err
	}
	for _, line := range strings.Split(string(blob), "\n") {
		if strings.HasPrefix(line, "systemd ") {
			var version int
			_, err := fmt.Sscanf(line, "systemd %d", &version)
			if err != nil {
				return 0, err
			}
			return version, nil
		}
	}
	return 0, fmt.Errorf("error parsing output from `systemctl --version`")
}

func findCmdInPath(pathlist []string, cmd, prefix string) (string, error) {
	if path.IsAbs(cmd) {
		return cmd, nil
	}

	for _, p := range pathlist {
		_, err := os.Lstat(path.Join(prefix, p, cmd))
		switch {
		case os.IsNotExist(err):
			continue
		case err != nil:
			return "", err
		}
		return path.Join(p, cmd), nil
	}
	return "", fmt.Errorf("%s not found in any of: %v", cmd, pathlist)
}
<commit_msg>engine/systemdnspawn: create /etc/machine-id for >= 230<commit_after>// Copyright 2015 The appc 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 systemdnspawn

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"
	"syscall"

	"github.com/appc/spec/schema/types"
)

var pathlist = []string{"/usr/local/sbin", "/usr/local/bin", "/usr/sbin",
	"/usr/bin", "/sbin", "/bin"}

type Engine struct{}

func (e Engine) Run(command string, args []string, environment types.Environment, chroot, workingDir string) error {
	nspawncmd := []string{"systemd-nspawn", "-D", chroot}

	systemdVersion, err := getSystemdVersion()
	if err != nil {
		return err
	}

	if systemdVersion >= 209 {
		nspawncmd = append(nspawncmd, "--quiet", "--register=no")
	}
	if workingDir != "" {
		if systemdVersion < 229 {
			return fmt.Errorf("the working dir can only be set on systems with systemd-nspawn >= 229")
		}
		nspawncmd = append(nspawncmd, "--chdir", workingDir)
	}
	if systemdVersion >= 230 {
		machineIdFile := path.Join(chroot, "/etc/machine-id")
		_, err := os.Stat(machineIdFile)
		switch {
		case os.IsNotExist(err):
			err := os.MkdirAll(path.Dir(machineIdFile), 0755)
			if err != nil {
				return err
			}
			f, err := os.Create(machineIdFile)
			if err != nil {
				return err
			}
			f.Close()
			defer os.RemoveAll(path.Join(chroot, machineIdFile))
		case err != nil:
			return err
		}
	}

	for _, envVar := range environment {
		nspawncmd = append(nspawncmd, "--setenv", envVar.Name+"="+envVar.Value)
	}

	nspawncmd = append(nspawncmd, "--setenv", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")

	abscmd, err := findCmdInPath(pathlist, command, chroot)
	if err != nil {
		return err
	}

	finfo, err := os.Lstat(path.Join(chroot, abscmd))
	switch {
	case os.IsNotExist(err):
		return fmt.Errorf("binary %q doesn't exist", abscmd)
	case err != nil:
		return err
	}

	if finfo.Mode()&os.ModeSymlink != 0 && systemdVersion < 228 {
		fmt.Fprintf(os.Stderr, "Warning: %q is a symlink, which systemd-nspawn version %d might error on\n", abscmd, systemdVersion)
	}

	nspawncmd = append(nspawncmd, abscmd)
	nspawncmd = append(nspawncmd, args...)

	execCmd := exec.Command(nspawncmd[0], nspawncmd[1:]...)
	execCmd.Stdin = os.Stdin
	execCmd.Stdout = os.Stdout
	execCmd.Stderr = os.Stderr
	execCmd.Env = []string{"SYSTEMD_LOG_LEVEL=err"}

	err = execCmd.Run()
	if err == exec.ErrNotFound {
		return fmt.Errorf("systemd-nspawn is required but not found")
	}
	if err == nil {
		return nil
	}
	if exitErr, ok := err.(*exec.ExitError); ok {
		code := exitErr.Sys().(syscall.WaitStatus).ExitStatus()
		return fmt.Errorf("non-zero exit code: %d", code)
	}
	return err
}

func getSystemdVersion() (int, error) {
	_, err := exec.LookPath("systemctl")
	if err == exec.ErrNotFound {
		return 0, fmt.Errorf("system does not have systemd")
	}

	blob, err := exec.Command("systemctl", "--version").Output()
	if err != nil {
		return 0, err
	}
	for _, line := range strings.Split(string(blob), "\n") {
		if strings.HasPrefix(line, "systemd ") {
			var version int
			_, err := fmt.Sscanf(line, "systemd %d", &version)
			if err != nil {
				return 0, err
			}
			return version, nil
		}
	}
	return 0, fmt.Errorf("error parsing output from `systemctl --version`")
}

func findCmdInPath(pathlist []string, cmd, prefix string) (string, error) {
	if path.IsAbs(cmd) {
		return cmd, nil
	}

	for _, p := range pathlist {
		_, err := os.Lstat(path.Join(prefix, p, cmd))
		switch {
		case os.IsNotExist(err):
			continue
		case err != nil:
			return "", err
		}
		return path.Join(p, cmd), nil
	}
	return "", fmt.Errorf("%s not found in any of: %v", cmd, pathlist)
}
<|endoftext|>


<commit_before>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglemock

import (
	"reflect"
)

// Action represents an action to be taken in response to a call to a mock
// method.
type Action interface {
	// Invoke runs the specified action, given the arguments to the mock method.
	// It returns zero or more values that may be treated as the return values of
	// the method. If the action doesn't return any values, it may return the nil
	// slice.
	Invoke(methodArgs []interface{}) []interface{}

	// CheckType returns an error iff the action is not able to deal with methods
	// of the specified type. The type's Kind must be Func.
	CheckType(signature reflect.Type) error
}
<commit_msg>Replaced Action.CheckTypes with Action.SetSignature.<commit_after>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglemock

import (
	"reflect"
)

// Action represents an action to be taken in response to a call to a mock
// method.
type Action interface {
	// Set the signature of the function with which this action is being used.
	// This must be called exactly once, before Invoke is called.
	SetSignature(signature reflect.Type) error

	// Invoke runs the specified action, given the arguments to the mock method.
	// It returns zero or more values that may be treated as the return values of
	// the method. If the action doesn't return any values, it may return the nil
	// slice.
	//
	// You must call SetSignature before calling Invoke.
	Invoke(methodArgs []interface{}) []interface{}
}
<|endoftext|>


<commit_before>package es_stdout

import (
	"errors"
	"github.com/mattn/go-colorable"
	"github.com/watermint/toolbox/essentials/log/esl"
	"github.com/watermint/toolbox/essentials/terminal/es_terminfo"
	"github.com/watermint/toolbox/quality/infra/qt_secure"
	"io"
	"io/ioutil"
	"os"
)

var (
	ErrorGeneralIOFailure = errors.New("general i/o error")
)

type Feature interface {
	IsQuiet() bool
	IsTest() bool
}

func newDefaultOut(test, quiet bool) io.WriteCloser {
	if quiet {
		return &syncOut{co: ioutil.Discard}
	}
	if test {
		if qt_secure.IsSecureEndToEndTest() {
			return &syncOut{co: ioutil.Discard}
		} else {
			return &syncOut{co: os.Stdout}
		}
	} else {
		if es_terminfo.IsOutColorTerminal() {
			return newWriteCloser(colorable.NewColorableStdout())
		} else {
			return newWriteCloser(os.Stdout)
		}
	}
}

func NewDiscard() io.WriteCloser {
	return &syncOut{co: ioutil.Discard}
}

func NewDefaultOut(feature Feature) io.WriteCloser {
	return newDefaultOut(feature.IsTest(), feature.IsQuiet())
}

func NewTestOut() io.WriteCloser {
	return newDefaultOut(true, false)
}

func NewDirectOut() io.WriteCloser {
	return newDefaultOut(false, false)
}

func newWriteCloser(co io.Writer) io.WriteCloser {
	return newSync(co)
}

func newSync(co io.Writer) io.WriteCloser {
	return &syncOut{
		co: co,
	}
}

type syncOut struct {
	co io.Writer
}

func (z syncOut) Write(p []byte) (n int, err error) {
	// Recovery option for I/O error
	// https://github.com/watermint/toolbox/issues/411
	defer func() {
		if r := recover(); err != nil {
			l := esl.Default()
			l.Debug("Recovery from the syncOut error", esl.Any("r", r))
			if errVal, ok := r.(error); ok {
				err = errVal
			} else {
				err = ErrorGeneralIOFailure
			}
		}
	}()
	return z.co.Write(p)
}

func (z syncOut) Close() error {
	return nil
}
<commit_msg>partial fix #421 : apply deadline (that is not working for Windows)<commit_after>package es_stdout

import (
	"errors"
	"github.com/mattn/go-colorable"
	"github.com/watermint/toolbox/essentials/terminal/es_terminfo"
	"github.com/watermint/toolbox/quality/infra/qt_secure"
	"io"
	"io/ioutil"
	"os"
	"time"
)

const (
	DefaultTimeout = 5 * time.Second
)

var (
	ErrorGeneralIOFailure = errors.New("general i/o error")
)

type Feature interface {
	IsQuiet() bool
	IsTest() bool
}

type deadlineWriter interface {
	io.WriteCloser
	SetDeadline(t time.Time) error
}

func newDefaultOut(test, quiet bool) io.WriteCloser {
	if quiet {
		return &syncOut{co: ioutil.Discard}
	}
	if test {
		if qt_secure.IsSecureEndToEndTest() {
			return &syncOut{co: ioutil.Discard}
		} else {
			return &syncOut{co: os.Stdout}
		}
	} else {
		if es_terminfo.IsOutColorTerminal() {
			return newWriteCloser(colorable.NewColorableStdout())
		} else {
			return newWriteCloser(os.Stdout)
		}
	}
}

func NewDiscard() io.WriteCloser {
	return &syncOut{co: ioutil.Discard}
}

func NewDefaultOut(feature Feature) io.WriteCloser {
	return newDefaultOut(feature.IsTest(), feature.IsQuiet())
}

func NewTestOut() io.WriteCloser {
	return newDefaultOut(true, false)
}

func NewDirectOut() io.WriteCloser {
	return newDefaultOut(false, false)
}

func newWriteCloser(co io.Writer) io.WriteCloser {
	return newSync(co)
}

func newSync(co io.Writer) io.WriteCloser {
	return &syncOut{
		co: co,
	}
}

type syncOut struct {
	co io.Writer
}

func (z syncOut) Write(p []byte) (n int, err error) {
	// Recovery option for I/O error
	// https://github.com/watermint/toolbox/issues/411
	defer func() {
		if r := recover(); err != nil {
			if errVal, ok := r.(error); ok {
				err = errVal
			} else {
				err = ErrorGeneralIOFailure
			}
		}
	}()

	if w, ok := z.co.(deadlineWriter); ok {
		_ = w.SetDeadline(time.Now().Add(DefaultTimeout))
	}
	return z.co.Write(p)
}

func (z syncOut) Close() error {
	return nil
}
<|endoftext|>


<commit_before>package core

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

	"chain/core/query"
	"chain/core/query/filter"
	"chain/errors"
	"chain/net/http/httpjson"
	"chain/protocol/bc"
)

// These types enforce the ordering of JSON fields in API output.
type (
	txinResp struct {
		Action          interface{} `json:"action"`
		AssetID         interface{} `json:"asset_id"`
		AssetAlias      interface{} `json:"asset_alias"`
		AssetTags       interface{} `json:"asset_tags"`
		Amount          interface{} `json:"amount"`
		IssuanceProgram interface{} `json:"issuance_program"`
		SpentOutput     interface{} `json:"spent_output"`
		AccountID       interface{} `json:"account_id"`
		AccountAlias    interface{} `json:"account_alias"`
		AccountTags     interface{} `json:"account_tags"`
		ReferenceData   interface{} `json:"reference_data"`
		AssetDefinition interface{} `json:"asset_definition"`
	}
	txoutResp struct {
		Action         interface{} `json:"action"`
		Position       interface{} `json:"position"`
		AssetID        interface{} `json:"asset_id"`
		AssetAlias     interface{} `json:"asset_alias"`
		AssetTags      interface{} `json:"asset_tags"`
		Amount         interface{} `json:"asset_amount"`
		AccountID      interface{} `json:"account_id"`
		AccountAlias   interface{} `json:"account_alias"`
		AccountTags    interface{} `json:"account_tags"`
		ControlProgram interface{} `json:"control_program"`
		ReferenceData  interface{} `json:"reference_data"`
	}
	txResp struct {
		ID            interface{} `json:"id"`
		Timestamp     interface{} `json:"timestamp"`
		BlockID       interface{} `json:"block_id"`
		BlockHeight   interface{} `json:"block_height"`
		Position      interface{} `json:"position"`
		ReferenceData interface{} `json:"reference_data"`
		Inputs        interface{} `json:"inputs"`
		Outputs       interface{} `json:"outputs"`
	}
)

// listTransactions is an http handler for listing transactions matching
// an index or an ad-hoc filter.
//
// POST /list-transactions
func (a *api) listTransactions(ctx context.Context, in requestQuery) (result page, err error) {
	var c context.CancelFunc
	timeout := in.Timeout.Duration
	if timeout != 0 {
		ctx, c = context.WithTimeout(ctx, timeout)
		defer c()
	}

	if in.EndTimeMS == 0 {
		in.EndTimeMS = bc.Millis(time.Now())
	}

	var (
		p     filter.Predicate
		after query.TxAfter
	)

	// Build the filter predicate.
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	// Either parse the provided `after` or look one up for the time range.
	if in.After != "" {
		after, err = query.DecodeTxAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	} else {
		after, err = a.indexer.LookupTxAfter(ctx, in.StartTimeMS, in.EndTimeMS)
		if err != nil {
			return result, err
		}
	}

	var asc bool
	if in.Order == "asc" {
		asc = true
	}

	limit := defGenericPageSize
	txns, nextAfter, err := a.indexer.Transactions(ctx, p, in.FilterParams, after, limit, asc)
	if err != nil {
		return result, errors.Wrap(err, "running tx query")
	}

	resp := make([]*txResp, 0, len(txns))
	for _, t := range txns {
		tjson, ok := t.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Transactions output", t)
		}
		if tjson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Transactions output")
		}
		var tx map[string]interface{}
		err = json.Unmarshal(*tjson, &tx)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Transactions output")
		}

		inp, ok := tx["inputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for inputs in Indexer.Transactions output", tx["inputs"])
		}

		var inputs []map[string]interface{}
		for i, in := range inp {
			input, ok := in.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for input %d in Indexer.Transactions output", in, i)
			}
			inputs = append(inputs, input)
		}

		outp, ok := tx["outputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for outputs in Indexer.Transactions output", tx["outputs"])
		}

		var outputs []map[string]interface{}
		for i, out := range outp {
			output, ok := out.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for output %d in Indexer.Transactions output", out, i)
			}
			outputs = append(outputs, output)
		}

		inResps := make([]*txinResp, 0, len(inputs))
		for _, in := range inputs {
			r := &txinResp{
				Action:          in["action"],
				AssetID:         in["asset_id"],
				AssetAlias:      in["asset_alias"],
				AssetTags:       in["asset_tags"],
				Amount:          in["amount"],
				IssuanceProgram: in["issuance_program"],
				SpentOutput:     in["spent_output"],
				AccountID:       in["account_id"],
				AccountAlias:    in["account_alias"],
				AccountTags:     in["account_tags"],
				ReferenceData:   in["reference_data"],
				AssetDefinition: in["asset_definition"],
			}
			inResps = append(inResps, r)
		}
		outResps := make([]*txoutResp, 0, len(outputs))
		for _, out := range outputs {
			r := &txoutResp{
				Action:         out["action"],
				Position:       out["position"],
				AssetID:        out["asset_id"],
				AssetAlias:     out["asset_alias"],
				AssetTags:      out["asset_tags"],
				Amount:         out["asset_amount"],
				AccountID:      out["account_id"],
				AccountAlias:   out["account_alias"],
				AccountTags:    out["account_tags"],
				ControlProgram: out["control_program"],
				ReferenceData:  out["reference_data"],
			}
			outResps = append(outResps, r)
		}
		r := &txResp{
			ID:            tx["id"],
			Timestamp:     tx["timestamp"],
			BlockID:       tx["block_id"],
			BlockHeight:   tx["block_height"],
			Position:      tx["position"],
			ReferenceData: tx["reference_data"],
			Inputs:        inResps,
			Outputs:       outResps,
		}
		resp = append(resp, r)
	}

	out := in
	out.After = nextAfter.String()
	return page{
		Items:    httpjson.Array(resp),
		LastPage: len(resp) < limit,
		Next:     out,
	}, nil
}

// listAccounts is an http handler for listing accounts matching
// an index or an ad-hoc filter.
//
// POST /list-accounts
func (a *api) listAccounts(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, errors.Wrap(err, "parsing acc query")
	}
	after := in.After

	// Use the filter engine for querying account tags.
	accounts, after, err := a.indexer.Accounts(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running acc query")
	}

	result := make([]*accountResponse, 0, len(accounts))
	for _, a := range accounts {
		var orderedKeys []accountKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, accountKey{
					RootXPub:              mapKey["root_xpub"],
					AccountXPub:           mapKey["account_xpub"],
					AccountDerivationPath: mapKey["account_derivation_path"],
				})
			}
		}
		r := &accountResponse{
			ID:     a["id"],
			Alias:  a["alias"],
			Keys:   orderedKeys,
			Quorum: a["quorum"],
			Tags:   a["tags"],
		}
		result = append(result, r)
	}

	// Pull in the accounts by the IDs
	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}

// POST /list-balances
func (a *api) listBalances(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}

	var p filter.Predicate
	var sumBy []filter.Field
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	for _, field := range in.SumBy {
		f, err := filter.ParseField(field)
		if err != nil {
			return result, err
		}
		sumBy = append(sumBy, f)
	}

	// TODO(jackson): paginate this endpoint.
	balances, err := a.indexer.Balances(ctx, p, in.FilterParams, sumBy, in.TimestampMS)
	if err != nil {
		return result, err
	}

	result.Items = httpjson.Array(balances)
	result.LastPage = true
	result.Next = in
	return result, nil
}

// This type enforces the ordering of JSON fields in API output.
type utxoResp struct {
	Action         interface{} `json:"action"`
	TransactionID  interface{} `json:"transaction_id"`
	Position       interface{} `json:"position"`
	AssetID        interface{} `json:"asset_id"`
	AssetAlias     interface{} `json:"asset_alias"`
	AssetTags      interface{} `json:"asset_tags"`
	Amount         interface{} `json:"asset_amount"`
	AccountID      interface{} `json:"account_id"`
	AccountAlias   interface{} `json:"account_alias"`
	AccountTags    interface{} `json:"account_tags"`
	ControlProgram interface{} `json:"control_program"`
	ReferenceData  interface{} `json:"reference_data"`
}

// POST /list-unspent-outputs
func (a *api) listUnspentOutputs(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}
	var p filter.Predicate
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	var after *query.OutputsAfter
	if in.After != "" {
		after, err = query.DecodeOutputsAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	}

	limit := defGenericPageSize
	outputs, nextAfter, err := a.indexer.Outputs(ctx, p, in.FilterParams, in.TimestampMS, after, limit)
	if err != nil {
		return result, errors.Wrap(err, "querying outputs")
	}

	resp := make([]*utxoResp, 0, len(outputs))
	for _, o := range outputs {
		ojson, ok := o.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Outputs output", o)
		}
		if ojson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Outputs output")
		}
		var out map[string]interface{}
		err = json.Unmarshal(*ojson, &out)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Outputs output")
		}
		r := &utxoResp{
			Action:         out["action"],
			TransactionID:  out["transaction_id"],
			Position:       out["position"],
			AssetID:        out["asset_id"],
			AssetAlias:     out["asset_alias"],
			AssetTags:      out["asset_tags"],
			Amount:         out["asset_amount"],
			AccountID:      out["account_id"],
			AccountAlias:   out["account_alias"],
			AccountTags:    out["account_tags"],
			ControlProgram: out["control_program"],
			ReferenceData:  out["reference_data"],
		}
		resp = append(resp, r)
	}

	outQuery := in
	outQuery.After = nextAfter.String()
	return page{
		Items:    resp,
		LastPage: len(resp) < limit,
		Next:     outQuery,
	}, nil
}

// listAssets is an http handler for listing assets matching
// an index or an ad-hoc filter.
//
// POST /list-assets
func (a *api) listAssets(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, err
	}
	after := in.After

	// Use the query engine for querying asset tags.
	var assets []map[string]interface{}
	assets, after, err = a.indexer.Assets(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running asset query")
	}

	result := make([]*assetResponse, 0, len(assets))
	for _, a := range assets {
		var orderedKeys []assetKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, assetKey{
					AssetPubkey:         mapKey["asset_pubkey"],
					RootXPub:            mapKey["root_xpub"],
					AssetDerivationPath: mapKey["asset_derivation_path"],
				})
			}
		}
		r := &assetResponse{
			ID:              a["id"],
			IssuanceProgram: a["issuance_program"],
			Keys:            orderedKeys,
			Quorum:          a["quorum"],
			Definition:      a["definition"],
			Tags:            a["tags"],
			Origin:          a["origin"],
		}
		if alias, ok := a["alias"].(string); ok && alias != "" {
			r.Alias = &alias
		}
		result = append(result, r)
	}

	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}
<commit_msg>core: fix missing output amounts in transaction object<commit_after>package core

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

	"chain/core/query"
	"chain/core/query/filter"
	"chain/errors"
	"chain/net/http/httpjson"
	"chain/protocol/bc"
)

// These types enforce the ordering of JSON fields in API output.
type (
	txinResp struct {
		Action          interface{} `json:"action"`
		AssetID         interface{} `json:"asset_id"`
		AssetAlias      interface{} `json:"asset_alias"`
		AssetTags       interface{} `json:"asset_tags"`
		Amount          interface{} `json:"amount"`
		IssuanceProgram interface{} `json:"issuance_program"`
		SpentOutput     interface{} `json:"spent_output"`
		AccountID       interface{} `json:"account_id"`
		AccountAlias    interface{} `json:"account_alias"`
		AccountTags     interface{} `json:"account_tags"`
		ReferenceData   interface{} `json:"reference_data"`
		AssetDefinition interface{} `json:"asset_definition"`
	}
	txoutResp struct {
		Action         interface{} `json:"action"`
		Position       interface{} `json:"position"`
		AssetID        interface{} `json:"asset_id"`
		AssetAlias     interface{} `json:"asset_alias"`
		AssetTags      interface{} `json:"asset_tags"`
		Amount         interface{} `json:"amount"`
		AccountID      interface{} `json:"account_id"`
		AccountAlias   interface{} `json:"account_alias"`
		AccountTags    interface{} `json:"account_tags"`
		ControlProgram interface{} `json:"control_program"`
		ReferenceData  interface{} `json:"reference_data"`
	}
	txResp struct {
		ID            interface{} `json:"id"`
		Timestamp     interface{} `json:"timestamp"`
		BlockID       interface{} `json:"block_id"`
		BlockHeight   interface{} `json:"block_height"`
		Position      interface{} `json:"position"`
		ReferenceData interface{} `json:"reference_data"`
		Inputs        interface{} `json:"inputs"`
		Outputs       interface{} `json:"outputs"`
	}
)

// listTransactions is an http handler for listing transactions matching
// an index or an ad-hoc filter.
//
// POST /list-transactions
func (a *api) listTransactions(ctx context.Context, in requestQuery) (result page, err error) {
	var c context.CancelFunc
	timeout := in.Timeout.Duration
	if timeout != 0 {
		ctx, c = context.WithTimeout(ctx, timeout)
		defer c()
	}

	if in.EndTimeMS == 0 {
		in.EndTimeMS = bc.Millis(time.Now())
	}

	var (
		p     filter.Predicate
		after query.TxAfter
	)

	// Build the filter predicate.
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	// Either parse the provided `after` or look one up for the time range.
	if in.After != "" {
		after, err = query.DecodeTxAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	} else {
		after, err = a.indexer.LookupTxAfter(ctx, in.StartTimeMS, in.EndTimeMS)
		if err != nil {
			return result, err
		}
	}

	var asc bool
	if in.Order == "asc" {
		asc = true
	}

	limit := defGenericPageSize
	txns, nextAfter, err := a.indexer.Transactions(ctx, p, in.FilterParams, after, limit, asc)
	if err != nil {
		return result, errors.Wrap(err, "running tx query")
	}

	resp := make([]*txResp, 0, len(txns))
	for _, t := range txns {
		tjson, ok := t.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Transactions output", t)
		}
		if tjson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Transactions output")
		}
		var tx map[string]interface{}
		err = json.Unmarshal(*tjson, &tx)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Transactions output")
		}

		inp, ok := tx["inputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for inputs in Indexer.Transactions output", tx["inputs"])
		}

		var inputs []map[string]interface{}
		for i, in := range inp {
			input, ok := in.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for input %d in Indexer.Transactions output", in, i)
			}
			inputs = append(inputs, input)
		}

		outp, ok := tx["outputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for outputs in Indexer.Transactions output", tx["outputs"])
		}

		var outputs []map[string]interface{}
		for i, out := range outp {
			output, ok := out.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for output %d in Indexer.Transactions output", out, i)
			}
			outputs = append(outputs, output)
		}

		inResps := make([]*txinResp, 0, len(inputs))
		for _, in := range inputs {
			r := &txinResp{
				Action:          in["action"],
				AssetID:         in["asset_id"],
				AssetAlias:      in["asset_alias"],
				AssetTags:       in["asset_tags"],
				Amount:          in["amount"],
				IssuanceProgram: in["issuance_program"],
				SpentOutput:     in["spent_output"],
				AccountID:       in["account_id"],
				AccountAlias:    in["account_alias"],
				AccountTags:     in["account_tags"],
				ReferenceData:   in["reference_data"],
				AssetDefinition: in["asset_definition"],
			}
			inResps = append(inResps, r)
		}
		outResps := make([]*txoutResp, 0, len(outputs))
		for _, out := range outputs {
			r := &txoutResp{
				Action:         out["action"],
				Position:       out["position"],
				AssetID:        out["asset_id"],
				AssetAlias:     out["asset_alias"],
				AssetTags:      out["asset_tags"],
				Amount:         out["amount"],
				AccountID:      out["account_id"],
				AccountAlias:   out["account_alias"],
				AccountTags:    out["account_tags"],
				ControlProgram: out["control_program"],
				ReferenceData:  out["reference_data"],
			}
			outResps = append(outResps, r)
		}
		r := &txResp{
			ID:            tx["id"],
			Timestamp:     tx["timestamp"],
			BlockID:       tx["block_id"],
			BlockHeight:   tx["block_height"],
			Position:      tx["position"],
			ReferenceData: tx["reference_data"],
			Inputs:        inResps,
			Outputs:       outResps,
		}
		resp = append(resp, r)
	}

	out := in
	out.After = nextAfter.String()
	return page{
		Items:    httpjson.Array(resp),
		LastPage: len(resp) < limit,
		Next:     out,
	}, nil
}

// listAccounts is an http handler for listing accounts matching
// an index or an ad-hoc filter.
//
// POST /list-accounts
func (a *api) listAccounts(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, errors.Wrap(err, "parsing acc query")
	}
	after := in.After

	// Use the filter engine for querying account tags.
	accounts, after, err := a.indexer.Accounts(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running acc query")
	}

	result := make([]*accountResponse, 0, len(accounts))
	for _, a := range accounts {
		var orderedKeys []accountKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, accountKey{
					RootXPub:              mapKey["root_xpub"],
					AccountXPub:           mapKey["account_xpub"],
					AccountDerivationPath: mapKey["account_derivation_path"],
				})
			}
		}
		r := &accountResponse{
			ID:     a["id"],
			Alias:  a["alias"],
			Keys:   orderedKeys,
			Quorum: a["quorum"],
			Tags:   a["tags"],
		}
		result = append(result, r)
	}

	// Pull in the accounts by the IDs
	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}

// POST /list-balances
func (a *api) listBalances(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}

	var p filter.Predicate
	var sumBy []filter.Field
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	for _, field := range in.SumBy {
		f, err := filter.ParseField(field)
		if err != nil {
			return result, err
		}
		sumBy = append(sumBy, f)
	}

	// TODO(jackson): paginate this endpoint.
	balances, err := a.indexer.Balances(ctx, p, in.FilterParams, sumBy, in.TimestampMS)
	if err != nil {
		return result, err
	}

	result.Items = httpjson.Array(balances)
	result.LastPage = true
	result.Next = in
	return result, nil
}

// This type enforces the ordering of JSON fields in API output.
type utxoResp struct {
	Action         interface{} `json:"action"`
	TransactionID  interface{} `json:"transaction_id"`
	Position       interface{} `json:"position"`
	AssetID        interface{} `json:"asset_id"`
	AssetAlias     interface{} `json:"asset_alias"`
	AssetTags      interface{} `json:"asset_tags"`
	Amount         interface{} `json:"amount"`
	AccountID      interface{} `json:"account_id"`
	AccountAlias   interface{} `json:"account_alias"`
	AccountTags    interface{} `json:"account_tags"`
	ControlProgram interface{} `json:"control_program"`
	ReferenceData  interface{} `json:"reference_data"`
}

// POST /list-unspent-outputs
func (a *api) listUnspentOutputs(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}
	var p filter.Predicate
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	var after *query.OutputsAfter
	if in.After != "" {
		after, err = query.DecodeOutputsAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	}

	limit := defGenericPageSize
	outputs, nextAfter, err := a.indexer.Outputs(ctx, p, in.FilterParams, in.TimestampMS, after, limit)
	if err != nil {
		return result, errors.Wrap(err, "querying outputs")
	}

	resp := make([]*utxoResp, 0, len(outputs))
	for _, o := range outputs {
		ojson, ok := o.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Outputs output", o)
		}
		if ojson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Outputs output")
		}
		var out map[string]interface{}
		err = json.Unmarshal(*ojson, &out)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Outputs output")
		}
		r := &utxoResp{
			Action:         out["action"],
			TransactionID:  out["transaction_id"],
			Position:       out["position"],
			AssetID:        out["asset_id"],
			AssetAlias:     out["asset_alias"],
			AssetTags:      out["asset_tags"],
			Amount:         out["amount"],
			AccountID:      out["account_id"],
			AccountAlias:   out["account_alias"],
			AccountTags:    out["account_tags"],
			ControlProgram: out["control_program"],
			ReferenceData:  out["reference_data"],
		}
		resp = append(resp, r)
	}

	outQuery := in
	outQuery.After = nextAfter.String()
	return page{
		Items:    resp,
		LastPage: len(resp) < limit,
		Next:     outQuery,
	}, nil
}

// listAssets is an http handler for listing assets matching
// an index or an ad-hoc filter.
//
// POST /list-assets
func (a *api) listAssets(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, err
	}
	after := in.After

	// Use the query engine for querying asset tags.
	var assets []map[string]interface{}
	assets, after, err = a.indexer.Assets(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running asset query")
	}

	result := make([]*assetResponse, 0, len(assets))
	for _, a := range assets {
		var orderedKeys []assetKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, assetKey{
					AssetPubkey:         mapKey["asset_pubkey"],
					RootXPub:            mapKey["root_xpub"],
					AssetDerivationPath: mapKey["asset_derivation_path"],
				})
			}
		}
		r := &assetResponse{
			ID:              a["id"],
			IssuanceProgram: a["issuance_program"],
			Keys:            orderedKeys,
			Quorum:          a["quorum"],
			Definition:      a["definition"],
			Tags:            a["tags"],
			Origin:          a["origin"],
		}
		if alias, ok := a["alias"].(string); ok && alias != "" {
			r.Alias = &alias
		}
		result = append(result, r)
	}

	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}
<|endoftext|>


<commit_before>package main_test

import (
	"io"
	"strings"

	. "github.com/rightscale/right_st"

	"github.com/go-yaml/yaml"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("ServerTemplate", func() {
	var script io.Reader

	Describe("Parse", func() {
		Context("With valid YAML", func() {
			script = strings.NewReader(`---
Name: Test ST
Description: Test ST Description
RightScripts:
  Boot:
    - Dummy.sh
  Operational:
    - Dummy2.sh
  Decommission:
    - Dummy3.sh
Inputs:
  SERVER_HOSTNAME: text:test.local
MultiCloudImages:
  - Href: /api/multi_cloud_images/403042003
`)
			It("should parse correctly", func() {
				st, err := ParseServerTemplate(script)
				Expect(err).To(Succeed())
				Expect(st).NotTo(BeNil())
				Expect(st.Name).To(Equal("Test ST"))
				Expect(st.Description).To(Equal("Test ST Description"))
				Expect(st.Inputs).To(Equal(map[string]*InputValue{"SERVER_HOSTNAME": &InputValue{Type: "text", Value: "test.local"}}))
				Expect(st.RightScripts["Boot"]).To(Equal([]string{"Dummy.sh"}))
				Expect(st.RightScripts["Operational"]).To(Equal([]string{"Dummy2.sh"}))
				Expect(st.RightScripts["Decommission"]).To(Equal([]string{"Dummy3.sh"}))
				Expect(st.MultiCloudImages).To(Equal([]*Image{
					&Image{Href: "/api/multi_cloud_images/403042003"},
				}))
			})
		})

		Context("With invalid structure in YAML", func() {
			It("should return an error", func() {
				script := strings.NewReader(`---
Name: Test ST
Description: Test ST Description
RightScripts:
  Boot:
    - Dummy.sh
Inputs:
  - TEXT_INPUT
# The Inputs field should have a map not an array
`)
				_, err := ParseServerTemplate(script)
				Expect(err).To(HaveOccurred())
				Expect(err).To(MatchError(&yaml.TypeError{
					Errors: []string{
						"line 8: cannot unmarshal !!seq into map[string]*main.InputValue",
					},
				}))
			})
		})

		Context("With invalid RightScript type in YAML", func() {
			It("should return an error", func() {
				script := strings.NewReader(`---
Name: Test ST
Description: Test ST Description
RightScripts:
  Unknown:
    - Dummy.sh
Inputs: {}
`)
				_, err := ParseServerTemplate(script)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).Should(ContainSubstring("not a valid sequence name"))
			})
		})

		Context("With an unknown field in YAML", func() {
			It("should return an error", func() {
				script := strings.NewReader(`---
Name: Test ST
Description: Test ST Description
Some Bogus Field: Some bogus value
`)
				_, err := ParseServerTemplate(script)
				Expect(err).To(HaveOccurred())
				Expect(err).To(MatchError(&yaml.TypeError{
					Errors: []string{
						"line 2: no such field 'Some Bogus Field' in struct 'main.ServerTemplate'",
					},
				}))
			})
		})
	})
})
<commit_msg>Fix servertemplate_test specs after struct rename<commit_after>package main_test

import (
	"io"
	"strings"

	. "github.com/rightscale/right_st"

	"github.com/go-yaml/yaml"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("ServerTemplate", func() {
	var script io.Reader

	Describe("Parse", func() {
		Context("With valid YAML", func() {
			script = strings.NewReader(`---
Name: Test ST
Description: Test ST Description
RightScripts:
  Boot:
    - Dummy.sh
  Operational:
    - Dummy2.sh
  Decommission:
    - Dummy3.sh
Inputs:
  SERVER_HOSTNAME: text:test.local
MultiCloudImages:
  - Href: /api/multi_cloud_images/403042003
  - Name: FooImage
    Revision: 100
`)
			It("should parse correctly", func() {
				st, err := ParseServerTemplate(script)
				Expect(err).To(Succeed())
				Expect(st).NotTo(BeNil())
				Expect(st.Name).To(Equal("Test ST"))
				Expect(st.Description).To(Equal("Test ST Description"))
				Expect(st.Inputs).To(Equal(map[string]*InputValue{"SERVER_HOSTNAME": &InputValue{Type: "text", Value: "test.local"}}))
				Expect(st.RightScripts["Boot"]).To(Equal([]string{"Dummy.sh"}))
				Expect(st.RightScripts["Operational"]).To(Equal([]string{"Dummy2.sh"}))
				Expect(st.RightScripts["Decommission"]).To(Equal([]string{"Dummy3.sh"}))
				Expect(st.MultiCloudImages).To(Equal([]*MultiCloudImage{
					&MultiCloudImage{Href: "/api/multi_cloud_images/403042003"},
					&MultiCloudImage{Name: "FooImage", Revision: 100},
				}))
			})
		})

		Context("With invalid structure in YAML", func() {
			It("should return an error", func() {
				script := strings.NewReader(`---
Name: Test ST
Description: Test ST Description
RightScripts:
  Boot:
    - Dummy.sh
Inputs:
  - TEXT_INPUT
# The Inputs field should have a map not an array
`)
				_, err := ParseServerTemplate(script)
				Expect(err).To(HaveOccurred())
				Expect(err).To(MatchError(&yaml.TypeError{
					Errors: []string{
						"line 8: cannot unmarshal !!seq into map[string]*main.InputValue",
					},
				}))
			})
		})

		Context("With invalid RightScript type in YAML", func() {
			It("should return an error", func() {
				script := strings.NewReader(`---
Name: Test ST
Description: Test ST Description
RightScripts:
  Unknown:
    - Dummy.sh
Inputs: {}
`)
				_, err := ParseServerTemplate(script)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).Should(ContainSubstring("not a valid sequence name"))
			})
		})

		Context("With an unknown field in YAML", func() {
			It("should return an error", func() {
				script := strings.NewReader(`---
Name: Test ST
Description: Test ST Description
Some Bogus Field: Some bogus value
`)
				_, err := ParseServerTemplate(script)
				Expect(err).To(HaveOccurred())
				Expect(err).To(MatchError(&yaml.TypeError{
					Errors: []string{
						"line 2: no such field 'Some Bogus Field' in struct 'main.ServerTemplate'",
					},
				}))
			})
		})
	})
})
<|endoftext|>