How to include go templates when unit testing - go

I'm trying to test an implementation of a simple web server in Go using the Gin.
The service has a single endpoint rendering HTML.
server.go
// Serve triggers the server initialization
func Serve(addr string) {
if err := serverEngine().Run(addr); err != nil {
log.Fatalf("could not serve on %s: %s", addr, err)
}
}
func serverEngine() *gin.Engine {
eng := gin.Default()
// Register resource handlers
eng.LoadHTMLGlob("tmpl/*")
eng.GET("/", indexHandler)
return eng
}
func indexHandler(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "foo",
})
}
server_test.go
func TestServe(t *testing.T) {
timeOut := time.Duration(3) * time.Second
server := httptest.NewServer(serverEngine())
defer server.Close()
// fixes weird double ':' problem
port := server.URL[len(server.URL)-5:]
_, err := net.DialTimeout("tcp", "localhost:"+port, timeOut)
if err != nil {
t.Errorf("failed to dial server: %s", err)
}
}
When I run the code everything works fine. But when running the unit tests it panics with the following message:
--- FAIL: TestServe (0.00s)
panic: html/template: pattern matches no files: `tmpl/*` [recovered]
panic: html/template: pattern matches no files: `tmpl/*`
Project structure:
.
├── main.go
└── server
├── server.go
├── server_test.go
└── tmpl
└── index.tmpl
How can I ensure that the go test know about the template location at runtime so I can perform this test?

EDIT:
After comment interaction, updating answer match directory structure.
gin-test/
├── main.go
└── server
├── server.go
├── server_test.go
└── tmpl
└── index.tmpl
And your server.go file on function serverEngine() has-
eng.LoadHTMLGlob("tmpl/*")
First you're in root directory: i.e. gin-test, run-
# Output:
jeeva#mb-pro:~/go-home/src/gin-test$ go test ./...
? gin-test [no test files]
ok gin-test/server 0.046s
Second you're in gin-test/server directory/package:
jeeva#mb-pro:~/go-home/src/gin-test/server$ go test
#Output:
It would produce the same output as below answer.
My best guess is 'problem could be your directory structure and files' and bit of test case code improvements.
I have just tried your code at my local with following directory structure:
$GOPATH/src/gin-test/
├── server.go
├── server_test.go
└── tmpl
└── index.tmpl
Then, I have updated your TestServe method to following:
Note: your test case works too, I just updated to demo the GET request and response.
func TestServe(t *testing.T) {
server := httptest.NewServer(serverEngine())
defer server.Close()
fmt.Println("Server URL:", server.URL)
resp, err := http.Get(server.URL)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("Error:", err)
fmt.Println("Response:", string(body))
}
Unit test output:
jeeva#mb-pro:~/go-home/src/gin-test$ go test
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] Loaded HTML Templates (1):
- index.tmpl
[GIN-debug] GET / --> gin-test.indexHandler (3 handlers)
Server URL: http://127.0.0.1:56989
[GIN] 2017/07/09 - 16:31:16 | 200 | 432.107µs | 127.0.0.1 | GET /
Error: <nil>
Response: <b>I'm here</b>
PASS
ok gin-test 0.104s
jeeva#mb-pro:~/go-home/src/gin-test$
Happy coding!

Related

Golang error for SQL when trying to create a new user

I am receiving this error in my new_user.go when creating a user using Golang packages viper and cobra. The error is as follows:
cannot use result (variable of type sql.Result) as error value in return statement: sql.Result does not implement error (missing method Error)
My code is broken into 2 files which talk to each other, here is the tree hierarchy:
.
├── Makefile
├── README.md
├── cli
│   ├── config.go
│   ├── db-creds.yaml
│   ├── go.mod
│   ├── go.sum
│   ├── new_user.go
│   └── root.go
├── docker-compose.yaml
├── go.work
└── main.go
To connect to the database, I created a YAML file db-creds.yaml to pull the creds for config.go. No errors are popping up here:
config.go file
package cli
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
"github.com/spf13/viper"
)
// var dialects = map[string]gorp.Dialect{
// "postgres": gorp.PostgresDialect{},
// "mysql": gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"},
// }
// initConfig reads in config file and ENV variables if set
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.AddConfigPath("./")
viper.SetConfigName("db-creds")
viper.SetConfigType("yaml")
}
// If a config file is found, read it in:
err := viper.ReadInConfig()
if err == nil {
fmt.Println("Fatal error config file: ", viper.ConfigFileUsed())
}
return
}
func getConnection() *sql.DB {
// Make sure we only accept dialects that were compiled in.
// dialect := viper.GetString("database.dialect")
// _, exists := dialects[dialect]
// if !exists {
// return nil, "", fmt.Errorf("Unsupported dialect: %s", dialect)
// }
// Will want to create another command that will use a mapping
// to connect to a preset db in the yaml file.
dsn := fmt.Sprintf("%s:%s#%s(%s)?parseTime=true",
viper.GetString("mysql-5.7-dev.user"),
viper.GetString("mysql-5.7-dev.password"),
viper.GetString("mysql-5.7-dev.protocol"),
viper.GetString("mysql-5.7-dev.address"),
)
viper.Set("database.datasource", dsn)
db, err := sql.Open("msyql", viper.GetString("database.datasource"))
if err != nil {
fmt.Errorf("Cannot connect to database: %s", err)
}
return db
}
The error that I put at the top is where the error appears when I return my result. I am implementing the flag option for cobra to use -n follow by the name to represent the new user being added`.
new_user.go file
package cli
import (
"log"
"github.com/sethvargo/go-password/password"
"github.com/spf13/cobra"
)
var name string
// newCmd represents the new command
var newCmd = &cobra.Command{
Use: "new",
Short: "Create a new a user which will accommodate the individuals user name",
Long: `Create a new a user that will randomize a password to the specified user`,
RunE: func(cmd *cobra.Command, args []string) error {
db := getConnection()
superSecretPassword, err := password.Generate(64, 10, 10, false, false)
result, err := db.Exec("CREATE USER" + name + "'#'%'" + "IDENTIFIED BY" + superSecretPassword)
if err != nil {
log.Fatal(err)
}
// Will output the secret password combined with the user.
log.Printf(superSecretPassword)
return result <---- Error is here
},
}
func init() {
rootCmd.AddCommand(newCmd)
newCmd.Flags().StringVarP(&name, "name", "n", "", "The name of user to be added")
_ = newCmd.MarkFlagRequired("name")
}
The main purpose of this project are three things:
1.) Create a new user,
2.) Give any user specific permissions
3.) Delete them.
That is my end goal. Going one step at a time and just ran into this error. Hope anyone can help me. Golang is new to me and started about 2 weeks ago.
Go gives you a pretty clear indication about what's going on. The RunE member of cobra expects its callback to return an error (or nil, in case of success).
Here, you are returning result, which is not an error, but a specific type returned by your sql query. This is what your function should look like.
RunE: func(cmd *cobra.Command, args []string) error {
db := getConnection()
superSecretPassword, err := password.Generate(64, 10, 10, false, false)
_, err := db.Exec("CREATE USER" + name + "'#'%'" + "IDENTIFIED BY" + superSecretPassword)
if err != nil {
// Don't Fatal uselessly, let cobra handle your error the way it
// was designed to do it.
return err
}
// Will output the secret password combined with the user.
log.Printf(superSecretPassword)
// Here is how you should indicate your callback terminated successfully.
// Error is an interface, so it accepts nil values.
return nil
}
If you need the result of the db.Exec command (which does not seem to be the case), you'll need to do all the required processing within your cobra callback, as it is not designed to return values to the main thread.
Error handling
A few bad practices about handling error I noticed in your code:
If a go function has an error return, don't panic or kill the program if something unexpected occurs (like you did with log.Fatal). Instead, use that error return to propagate the error to the main thread, and let it decide what to do.
On the other hand, don't return results if something went wrong. Your getConnection function should be able to return an error if it fails: func getConnection() (*sql.DB, error). You should then handle this error in your RunE function, instead of just logging it and processing normally.

golang app with embed static files using reverse proxy

The code first.
package main
import (
"log"
"io/fs"
"embed"
"net/http"
"github.com/gorilla/mux"
)
//go:embed static/*
var static embed.FS
func main() {
router := mux.NewRouter()
fSys, err := fs.Sub(static, "static")
if err != nil {
panic(err)
}
staticServer := http.FileServer(http.FS(fSys))
router.Handle("/", staticServer)
if err := http.ListenAndServe(":3001", router); err != nil {
log.Fatalf("server could not run")
}
}
So I have this application and two others app2, app3 on ports 3002 and 3003 accordingly.
I need to proxy those apps using apache2 server.
Urls should be http:///app1 http:///app2 http:///app3
If I write:
ProxyPass /app1 http://localhost:3001/
ProxyPassReverse /app1 http://localhost:3001/
so index.html file loads fine but css style not loaded.
html and css files are on the same "virtual" disk. How can I fix css loading?
├── go.mod
├── go.sum
├── main.go
└── static
├── index.html
└── style.css
index.html looks like
<link rel="stylesheet" href="./style.css">
<h1>Hello</h1>
Error: mesage in browser console:
The resource from “http://localhost:3001/style.css” was blocked due to MIME type (“text/plain”) mismatch (X-Content-Type-Options: nosniff)

Golang template can't access file in embedFS

my golang code arch is as bellow:
├── embeded.go
├── go.mod
├── json
│   └── file
└── main.go
and this is my embede.go code:
package main
import "embed"
//go:embed json/*
var templatesFS embed.FS
func TemplatesFS() embed.FS {
return templatesFS
}
now in my main.go I can't access file in json directory:
package main
import (
"fmt"
"log"
"os"
"text/template"
)
func main() {
tmpl := template.Must(
template.New("json/file").
ParseFS(TemplatesFS(), "json/file"))
if err := tmpl.Execute(os.Stdout, "config"); err != nil {
log.Fatal(err)
}
}
when I run above code I got error template: json/file: "json/file" is an incomplete or empty template
but I can access the file like this:
file, err := TemplatesFS().ReadFile("json/file")
so why I can't access it in templte.execute ?
How Can I Solve It ?
The template parser successfully read a template from the embedded file system.
The Execute method reports that the template tmpl is incomplete. The variable tmpl is set to the template created by the call to New. The template is incomplete because the application did not parse a template with the template's name, json/file.
ParseFS names templates using the file's base name. Fix by using using the file's base name in the call to New.
tmpl := template.Must(
template.New("file").ParseFS(TemplatesFS(), "json/file"))

How do you use Go 1.16 embed features in subfolders/packages?

Go 1.16 is out and I want to use the new embed features. I can get it to work if everything is in the main package. But it's not clear how to handle accessing resources from subfolders/packages. Trying to do it with embed.FS support.
e.g. I have a main.go and also an HTTP handler in a handlers package/folder
If I put the handler in the main, it works. If I put it in the handlers package, it can't find the templates. I get:
handlers/indexHandler.go:11:12: pattern templates: no matching files found exit status 1
Similarly, I can get it to serve an image from the static folder if I serve it from /. But I can't serve both a handler from / and the static/images from /. If I put images on /static/ it can't find the images.
I think it has to do with relative paths. But I can't find the right combination through trial and error... Could it be too early to rely on these features?
Previously I was using go-rice and I did not have these problems. But I would like to use the std library as much as possible.
main.go:
package main
import (...)
//go:embed static
var static embed.FS
func main() {
fsRoot, _ := fs.Sub(static, "static")
fsStatic := http.FileServer(http.FS(fsRoot))
http.Handle("/", fsStatic)
http.HandleFunc("/index", Index)
http.ListenAndServe(":8080", nil)
}
handlers/indexHandler.go:
package handlers
import (...)
//go:embed templates
var templates embed.FS
// Index handler
func Index(w http.ResponseWriter, r *http.Request) {
tmpl := template.New("")
var err error
if tmpl, err = tmpl.ParseFS(templates, "simple.gohtml"); err != nil {
fmt.Println(err)
}
if err = tmpl.ExecuteTemplate(w, "simple", nil); err != nil {
log.Print(err)
}
}
Structure is as follows...
.
├── go.mod
├── go.sum
├── handlers
│ └── indexHandler.go
├── main.go
├── static
│ ├── css
│ │ └── layout.css
│ └── images
│ └── logo.png
└── templates
└── simple.gohtml
I finally figured it out...
You can keep the templates folder in the main folder and embed them from there. Then you need to inject the FS variable into the other handler package. It's always easy after you figure it out.
e.g.
package main
//go:embed templates/*
var templateFs embed.FS
func main() {
handlers.TemplateFs = templateFs
...
package handlers
var TemplateFs embed.FS
func handlerIndex() {
...
tmpl, err = tmpl.ParseFS(TemplateFs, "templates/layout.gohtml",...
...
Currently in Go 1.17 , embed is not supporting subfolders/packages, see https://github.com/golang/go/issues/41191#issuecomment-686616556

Importing local package in another folder

I keep getting the following error and I have been trying out some of the other suggestions in other Stack Overflow threads.
./main.go:15:13: undefined: checkEnv
Folder Structure:
├── README.md
├── main.go
└── validate
└── args.go
$GOPATH
/Users/myusername/go
main.go
package main
import (
"fmt"
"log"
"os"
"projectName/validate"
"gopkg.in/urfave/cli.v1"
)
func main() {
app := cli.NewApp()
app.Name = "thirsty"
app.Action = func(c *cli.Context) error {
result := checkEnv(c.Args().Get(0))
fmt.Println(result)
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
validate/args.go
package validate
import "strings"
func checkEnv(environment string) bool {
env := strings.ToLower(environment)
return env != "qa" && env != "dev"
}
My project is in the src directory of my $GOPATH. If this is not the proper way to do code splitting, is there a generally used convention to follow?
Any feedback appreciated.
There are two problems here.
The checkEnv method is not exported [in validate/args.go]. ie it is usable only inside the same package and not other packages To export just capitalize the first letter of the method CheckEnv. Now CheckEnv can be used from other packages
While calling a method from other package, the syntax should be packagename.ExportedMethod(params...). so in your code [in main.go] it should be result := validate.CheckEnv(c.Args().Get(0))

Resources