Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generated mock not importing (and not able to import) package, but also can't run tests #290

Closed
phekno opened this issue Jun 17, 2020 · 3 comments

Comments

@phekno
Copy link

phekno commented Jun 17, 2020

I have this code that I'm trying to generate a mock of:

package bamboo

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

// Bambooer is an interface to cover working with the Bamboo struct and methods
type Bambooer interface {
	CallWithRawResponse(uri string, method supportedMethod, authQueryStringNeeded bool) (*http.Response, error)
	CallWithStringResponse(uri string, method supportedMethod, authQueryStringNeeded bool) (string, error)
	GetExistingAgents()
	GetNewAgents()
	GetBuildProjects()
	GetDeployProjects()
}

// Bamboo struct holds metadata about the target Bamboo endpoint, as well as methods to interact with it
type Bamboo struct {
	URL                 string
	AuthTypeQueryString string
	Username            string
	Password            string
	ProjectID           string
	ProjectType         string
	ExistingAgents      []Agent
	NewAgents           []agentAuth
	BuildProjects       []project
	DeployProjects      []deployProject
	DeployProjectType   string
}

// CallWithRawResponse wraps a ReST based API call to a Bamboo endpoint with authentication and query data, and returns the raw response
func (b *Bamboo) CallWithRawResponse(uri string, method supportedMethod, authQueryStringNeeded bool) (*http.Response, error) {
	// Setup HTTP client
	client := &http.Client{}

	// Validate accepted method
	if err := method.isValid(); err != nil {
		return nil, err
	}

	// Setup the HTTP request params
	var targetURL string
	if authQueryStringNeeded {
		targetURL = fmt.Sprintf("%s%s%s", b.URL, uri, b.AuthTypeQueryString)
	} else {
		targetURL = fmt.Sprintf("%s%s", b.URL, uri)
	}

	req, err := http.NewRequest(string(method), targetURL, nil)
	req.SetBasicAuth(b.Username, b.Password)

	// Carry out HTTP call and pass errors up
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

// CallWithStringResponse wraps a ReST based API call to a Bamboo endpoint with authentication and query data, and returns the raw response
func (b *Bamboo) CallWithStringResponse(uri string, method supportedMethod, authQueryStringNeeded bool) (string, error) {
	// Setup HTTP client
	client := &http.Client{}

	// Validate accepted method
	if err := method.isValid(); err != nil {
		return "", err
	}

	// Setup the HTTP request params
	var targetURL string
	if authQueryStringNeeded {
		targetURL = fmt.Sprintf("%s%s%s", b.URL, uri, b.AuthTypeQueryString)
	} else {
		targetURL = fmt.Sprintf("%s%s", b.URL, uri)
	}

	req, err := http.NewRequest(string(method), targetURL, nil)
	req.SetBasicAuth(b.Username, b.Password)

	// Carry out HTTP call and pass errors up
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	// Convert response body to string before returning
	bodyText, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(bodyText), nil
}

// GetExistingAgents returns data about existing Bamboo build agents
func (b *Bamboo) GetExistingAgents() {
	var allAgents []Agent
	var a []Agent

	resp, err := b.CallWithStringResponse("agent.json", GET, true)
	if err != nil {
		fmt.Print(err)
	}

	jsonerr := json.Unmarshal([]byte(resp), &allAgents)
	if jsonerr != nil {
		fmt.Println(jsonerr)
	}

	for _, agent := range allAgents {
		if strings.Contains(agent.AgentName, "agent_name_") {
			a = append(a, agent)
		}
	}

	b.ExistingAgents = a
}

// GetNewAgents returns an updated list of Agents from the Bamboo endpoint and updates the Bamboo struct
func (b *Bamboo) GetNewAgents() {
	var allAgents []agentAuth
	var a []agentAuth

	resp, err := b.CallWithStringResponse("agent/authentication.json", GET, true)
	if err != nil {
		fmt.Print(err)
	}

	jsonerr := json.Unmarshal([]byte(resp), &allAgents)
	if jsonerr != nil {
		fmt.Println(jsonerr)
	}

	for _, agent := range allAgents {
		if strings.Contains(agent.IP, "10.100.1.") && !agent.Approved {
			a = append(a, agent)
		}
	}

	b.NewAgents = a
}

// GetBuildProjects gets a list of Build Projects from the Bamboo endpoint and updates the Bamboo struct
func (b *Bamboo) GetBuildProjects() {
	var buildProjects projects
	var bp []project

	// Create URI
	uri := fmt.Sprintf("project.json")

	// Make the call and handle errors
	resp, err := b.CallWithStringResponse(uri, GET, true)
	if err != nil {
		fmt.Print(err)
	}

	jsonerr := json.Unmarshal([]byte(resp), &buildProjects)
	if jsonerr != nil {
		fmt.Println(jsonerr)
	}

	var x project
	for _, build := range buildProjects.Projects.Project {
		if strings.Contains(build.Name, "BUILD_") {
			x.Description = build.Description
			x.Key = build.Key
			x.Name = build.Name
			x.Link = build.Link.Href
			bp = append(bp, x)
		}
	}

	b.BuildProjects = bp
}

// GetDeployProjects gets a list of Deploy Projects from the Bamboo endpoint and updates the Bamboo struct
func (b *Bamboo) GetDeployProjects() {
	var deployProjects []deployProject
	var d []deployProject

	// Create URI
	uri := fmt.Sprintf("deploy/project/all.json")

	// Make the call and handle errors
	resp, err := b.CallWithStringResponse(uri, GET, true)
	if err != nil {
		fmt.Print(err)
	}

	jsonerr := json.Unmarshal([]byte(resp), &deployProjects)
	if jsonerr != nil {
		fmt.Println(jsonerr)
	}

	for _, deploy := range deployProjects {
		if strings.Contains(deploy.Name, "DEPLOY_") {
			d = append(d, deploy)
		}
	}

	b.DeployProjects = d
}

The mock that gets generated looks like this:

// Code generated by mockery v2.0.0-alpha.13. DO NOT EDIT.

package mocks

import (
	http "net/http"

	mock "github.com/stretchr/testify/mock"

)

// Bambooer is an autogenerated mock type for the Bambooer type
type Bambooer struct {
	mock.Mock
}

// CallWithRawResponse provides a mock function with given fields: uri, method, authQueryStringNeeded
func (_m *Bambooer) CallWithRawResponse(uri string, method bamboo.supportedMethod, authQueryStringNeeded bool) (*http.Response, error) {
	ret := _m.Called(uri, method, authQueryStringNeeded)

	var r0 *http.Response
	if rf, ok := ret.Get(0).(func(string, bamboo.supportedMethod, bool) *http.Response); ok {
		r0 = rf(uri, method, authQueryStringNeeded)
	} else {
		if ret.Get(0) != nil {
			r0 = ret.Get(0).(*http.Response)
		}
	}

	var r1 error
	if rf, ok := ret.Get(1).(func(string, bamboo.supportedMethod, bool) error); ok {
		r1 = rf(uri, method, authQueryStringNeeded)
	} else {
		r1 = ret.Error(1)
	}

	return r0, r1
}

// CallWithStringResponse provides a mock function with given fields: uri, method, authQueryStringNeeded
func (_m *Bambooer) CallWithStringResponse(uri string, method bamboo.supportedMethod, authQueryStringNeeded bool) (string, error) {
	ret := _m.Called(uri, method, authQueryStringNeeded)

	var r0 string
	if rf, ok := ret.Get(0).(func(string, bamboo.supportedMethod, bool) string); ok {
		r0 = rf(uri, method, authQueryStringNeeded)
	} else {
		r0 = ret.Get(0).(string)
	}

	var r1 error
	if rf, ok := ret.Get(1).(func(string, bamboo.supportedMethod, bool) error); ok {
		r1 = rf(uri, method, authQueryStringNeeded)
	} else {
		r1 = ret.Error(1)
	}

	return r0, r1
}

// GetBuildProjects provides a mock function with given fields:
func (_m *Bambooer) GetBuildProjects() {
	_m.Called()
}

// GetDeployProjects provides a mock function with given fields:
func (_m *Bambooer) GetDeployProjects() {
	_m.Called()
}

// GetExistingAgents provides a mock function with given fields:
func (_m *Bambooer) GetExistingAgents() {
	_m.Called()
}

// GetNewAgents provides a mock function with given fields:
func (_m *Bambooer) GetNewAgents() {
	_m.Called()
}

The linter is complaining, and tests won't run, because the bamboo package hasn't been imported. When I try to import it, and save the file, the linter removes the import statement, and it still complains that bamboo is undefined (e.g. the parameter method which has a type bamboo.supportedMethod is undefined). It's defined in the bamboo package, and in the other mocks, that package is imported and works fine:

// Code generated by mockery v2.0.0-alpha.13. DO NOT EDIT.

package mocks

import (
	bamboo "bitbucket.domain.name.com/project/bamboo-agent-connection-manager/internal/bamboo"
	mock "github.com/stretchr/testify/mock"
)

// Agenter is an autogenerated mock type for the Agenter type
type Agenter struct {
	mock.Mock
}

// DedicateAgent provides a mock function with given fields: bt
func (_m *Agenter) DedicateAgent(bt *bamboo.Bamboo) {
	_m.Called(bt)
}

// DisableAgent provides a mock function with given fields: bt
func (_m *Agenter) DisableAgent(bt *bamboo.Bamboo) {
	_m.Called(bt)
}

Here's the code that generated that mock:

package bamboo

import (
	"fmt"
	"strconv"
)

// Agenter is an interface to work with the agent struct and its methods
type Agenter interface {
	DisableAgent(bt *Bamboo)
	DedicateAgent(bt *Bamboo)
}

// Agent is the JSON structure we get back from Bamboo when we make a call to the /agent service
type Agent struct {
	ID        int    `json:"id"`
	AgentName string `json:"name"`
	AgentType string `json:"type"`
	Active    bool   `json:"active"`
	Enabled   bool   `json:"enabled"`
	Busy      bool   `json:"busy"`
}

// DisableAgent is a method used to disable a Bamboo build agent
func (a *Agent) DisableAgent(bt *Bamboo) {
	// Create URI
	uri := fmt.Sprintf("agent/%s", strconv.Itoa(a.ID))

	resp, err := bt.CallWithRawResponse(uri, DELETE, true)
	if err != nil {
		fmt.Print(err)
	}

	fmt.Println("Disabled " + a.AgentName)
	fmt.Println("Bamboo returned a: " + resp.Status)
}

// DedicateAgent dedicates agent a to project p (which has a project type t)
func (a *Agent) DedicateAgent(bt *Bamboo) {
	// Create URI
	uri := fmt.Sprintf("agent/assignment?executorType=AGENT&executorId=%s&assignmentType=%s&entityId=%s", strconv.Itoa(a.ID), bt.ProjectType, bt.ProjectID)

	// Make the call and handle errors
	resp, err := bt.CallWithRawResponse(uri, POST, false)
	if err != nil {
		fmt.Print(err)
	}

	fmt.Print("Dedicating ", a.AgentName, " to ", bt.ProjectType, " ", bt.ProjectID)
	fmt.Print(" returned ")
	fmt.Println(string(resp.Status))
}

// AgentAuther is an interface to work with the agentAuth struct and its methods
type AgentAuther interface {
	AuthenticateAgent(bt *Bamboo)
}

// This is the JSON structure we get back from Bamboo when we make a call to /agent/authentication
type agentAuth struct {
	UUID       string   `json:"uuid"`
	IP         string   `json:"ip"`
	Approved   bool     `json:"approved"`
	IPPatterns []string `json:"ipPatterns"`
}

func (agent *agentAuth) AuthenticateAgent(bt *Bamboo) {
	// Create URI
	uri := fmt.Sprintf("agent/authentication/%s", agent.UUID)

	// Make the call and handle errors
	resp, err := bt.CallWithRawResponse(uri, PUT, false)
	if err != nil {
		fmt.Print(err)
	}

	fmt.Print("Authenticating ", agent.IP)
	fmt.Println(" Bamboo returned a: " + resp.Status)
}

Any suggestions?

@LandonTClipp
Copy link
Contributor

Possible duplicate of #291

@LandonTClipp
Copy link
Contributor

LandonTClipp commented Jun 17, 2020

So you're running into an issue where you have unexported types in an exported interface. It's technically legal golang, but it's really bad practice.

type Bambooer interface {
	CallWithRawResponse(uri string, method supportedMethod, authQueryStringNeeded bool) (*http.Response, error)
	CallWithStringResponse(uri string, method supportedMethod, authQueryStringNeeded bool) (string, error)
	GetExistingAgents()
	GetNewAgents()
	GetBuildProjects()
	GetDeployProjects()
}

There's not really any way around this besides making your mocks part of your package --inpkg. The above interface cannot be mocked outside of the package because of the unexported supportedMethod type.

@phekno
Copy link
Author

phekno commented Jun 18, 2020

Oh! Sorry! Thanks for catching that!

@phekno phekno closed this as completed Jun 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants