Kindless ancestor query for different entity kinds in golang - google-app-engine

According to the documentation, it should be possible to retrieve an ancestor and all of its descendants, regardless of their kind.
In my implementation, I have a different kind of ancestor and descendant. The following codes however always returns the error "invalid entity type":
q := datastore.NewQuery("").Ancestor(tomKey)
t := q.Run(ctx)
for {
var x interface{}
_, err := t.Next(&x)
if err == datastore.Done {
break
}
if err != nil {
log.Errorf(ctx, "Error fetching entity: %v", err)
break
}
}
It seems that the call to t.Next(&x) expects a specific type instead of an empty interface. Would somebody please help me to resolve this problem?

I don't know the documentation is wrong, but you can use datastore.PropertyList to fetch arbitrary value. like this:
var v datastore.PropertyList
key, err := iter.Next(&v)
...
props, err := v.Save()
...
See this docs for more information.

Related

How can i mock database calls without a library?

i've been trying to wrap my head around unit testing, dependency injection, tdd and all that stuff and i've been stuck on testing functions that make database calls, for example.
Let's say you have a PostgresStore struct that takes in a Database interface, which has a Query() method.
type PostgresStore struct {
db Database
}
type Database interface {
Query(query string, args ...interface{}) (*sql.Rows, error)
}
And your PostgresStore has a GetPatients method, which calls database query.
func (p *PostgresStore) GetPatients() ([]Patient, error) {
rows, err := p.db.Query("SELECT id, name, age, insurance FROM patients")
if err != nil {
return nil, err
}
defer rows.Close()
items := []Patient{}
for rows.Next() {
var i Patient
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Surname,
&i.Age,
&i.InsuranceCompany,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
In the real implementation, you would just pass a *sql.DB as Database argument, but how would you guys write a unit test with a fake database struct?
let me try to clarify some of your doubts. First of all, I'm gonna share a working example to better understand what's going on. Then, I'm gonna mention all of the relevant aspects.
repo/db.go
package repo
import "database/sql"
type Patient struct {
ID int
Name string
Surname string
Age int
InsuranceCompany string
}
type PostgresStore struct {
// rely on the generic DB provided by the "sql" package
db *sql.DB
}
func (p *PostgresStore) GetPatient(id int) ([]Patient, error) {
rows, err := p.db.Query("SELECT id, name, age, insurance FROM patients")
if err != nil {
return nil, err
}
defer rows.Close()
items := []Patient{}
for rows.Next() {
var i Patient
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Surname,
&i.Age,
&i.InsuranceCompany,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
Here, the only relevant change is how you define the PostgresStore struct. As the db field, you should rely on the generic DB provided by the database/sql package of the Go Standard Library. Thanks to this, it's trivial to swap its implementation with a fake one, as we're gonna see later.
Please note that in the GetPatient method you're accepting an id parameter but you're not using it. Your query is more suitable to a method like GetAllPatients or something like that. Be sure to fix it accordingly.
repo/db_test.go
package repo
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestGetPatient(t *testing.T) {
// 1. set up fake db and mock
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("err not expected: %v", err)
}
// 2. configure the mock. What we expect (query or command)? The outcome (error vs no error).
rows := sqlmock.NewRows([]string{"id", "name", "surname", "age", "insurance"}).AddRow(1, "john", "doe", 23, "insurance-test")
mock.ExpectQuery("SELECT id, name, age, insurance FROM patients").WillReturnRows(rows)
// 3. instantiate the PostgresStore with the fake db
sut := &PostgresStore{
db: db,
}
// 4. invoke the action we've to test
got, err := sut.GetPatient(1)
// 5. assert the result
assert.Nil(t, err)
assert.Contains(t, got, Patient{1, "john", "doe", 23, "insurance-test"})
}
Here, there are a lot to cover. First, you can check the comments within the code that give you a better idea of each step. In the code, we're relying on the package github.com/DATA-DOG/go-sqlmock that allows us to easily mock a database client.
Obviously, the purpose of this code is to give a general idea on how to implement your needs. It can be written in a better way but it can be a good starting point for writing tests in this scenario.
Let me know if this helps, thanks!

Golang: map with multiple values per key

func ParseportsFromFile(file string) (map[string]string, error) {
buf, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
ret := [make(map[string]string)]
rows := strings.Split(string(buf), "\n")
for _, row := range rows {
kvs := strings.SplitN(row, "=", 2)
if len(kvs) == 2 {
ret[strings.TrimSpace(kvs[0])] = strings.TrimSpace(kvs[1])
}
}
return ret, nil
}
This function allows me to read a file like that :
user1=123
user1=321
user2=124
However, the data return is
map[user1:321 user2:124]
So that mean user1=123 has been overwritten with user1=321
How to avoid that ?
How to create an array like
map[user1:[123,321], user2: 124]
to avoid an item to overwrite another ?
Since go is strongly typed, it would be easier to make it right away a map of slices. See the http.Header type, for example. They had the same problem to solve when designing it.
In your case, this could look something like this:
result := make(map[string][]string)
for _, row := range rows {
parts := strings.Split(row, "=")
key := parts[0]
value := parts[1]
result[key] = append(result[key], value)
}
https://go.dev/play/p/5uRH-aQmATR
Otherwise, you need to use interface{} (any) so you can have both, string and []string, but the logic to get that done would be more complex, and using it would be also more complex since you always need to check what it is and do type assertion and so on. After all, I would not recommend it.

Does aetest.NewContext() require arguments?

I am learning how to create Golang tests for an Appengine app.
The documentation examples don't make sense to me.
https://cloud.google.com/appengine/docs/standard/go/tools/localunittesting/reference
Documentation seems to say you can create a context := aetest.NewContext()
When I attempt to do so, I'm getting an error that aetest.NewContext requires arguments.
$ go test -v
./skincare_test.go:12: not enough arguments in call to aetest.NewContext
have ()
want (*aetest.Options)
./skincare_test.go:12: assignment count mismatch: 3 = 2
FAIL _/Users/Bryan/work/gocode/skincarereview [build failed]
content of skincare_test.go:
package skincare
import (
"net/http"
"net/http/httptest"
"testing"
"appengine/aetest"
)
func TestIndexHandler(t *testing.T) {
ctx, done, err := aetest.NewContext()
if err != nil {
t.Fatal(err)
}
defer done()
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(root)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
expected := "<div>Name"
if rr.Body.String() != expected {
t.Errorf("handler returned expected body: got %v want %v",
rr.Body.String(), expected)
}
}
I learn best by looking at example code, where can I find examples of Tests for Go web applications that use Appengine datastore?
The examples in the documentation are so simple that I don't see how I'm supposed to do more complicated testing.
It says 2 things:
1) You are missing a required parameter *aetest.Options
2) that you can NOT assign result aetest.NewContext() that consist of 2 variable to a set of 3 variables.
Check what is the output of the function. I guess it is just (context.Context, error) - I suspect the done is moved to the *aetest.Options somehow.
Unfortunately my access to docs is blocked right now.
You are using the old version of the app engine package (appengine/aetest instead of google.golang.org/appengine/aetest). The newer version does not require arguments.

Weird datastore error in Go, "The kind is the empty string"

I am recently getting an error that I have never seen before when making a simple datastore.GetAll() request. I can't figure out what it means and I can't find any documentation with the error message or any help from Googleing the error message.
Here's my code:
type MyUnderlyingStruct struct {
ApplyTo *datastore.Key
ApplyFrom *datastore.Key
Amount float64
LocationKey *datastore.Key
DepartmentKey *datastore.Key
SubjectAreaKey *datastore.Key
}
type MyStruct []MyUnderlyingStruct
//In the case where I get the error someKey is a valid, complete Key value
// of a different kind that what we are querying for and there is actually
// an entity in my datastore that matches this query
func (x *MyStruct) Load(w http.ResponseWriter, r *http.Request, someKey *datastore.Key) (error) {
c := appengine.NewContext(r)
q := datastore.NewQuery("MyUnderlyingStruct_KindName").Order("-Amount")
if someKey != nil { q = q.Filter("ApplyTo=", someKey) }
keys, err := q.GetAll(c,x)
if _, ok := err.(*datastore.ErrFieldMismatch); ok { err = nil }
if err != nil && err != datastore.Done {return err}
return nil
}
Which returns this error:
API error 1 (datastore_v3: BAD_REQUEST): The kind is the empty string.
Can anyone tell me why I am getting this error, or what it is trying to tell me?
Looking at your issue on the first glance (because I am not familiar with Google's datastore API), it seems to me the problem is a result of zeroed-memory initialization using new keyword.
When a struct is created with the keyword without assigning starting values for the fields, 0's are given as default. When mapped to string, it's "" (empty). Go actually threw a very helpful error for you.
As you have pointed out, you have used Mykey := new(datastore.Key). Thanks for your generosity and this can serve as answer for future users.

Get an entity by a key passed via GET parameter

I have
http://localhost:8080/?key=ahFkZXZ-ZGV2LWVkdW5hdGlvbnIOCxIIVXNlckluZm8YLAw
I would like to ask on how to:
Decode and convert the "key" to a *datastore.Key
And use it to get an entity.
Thanks for your help!
First: You should think about which packages you need this case. Since you're trying to read a GET value from a URL you need probably a function from net/http.
In particular: FormValue(key string) returns GET and POST parameters.
Second: Now open the appengine/datastore documentation and find functions which do the following:
Decode a string to a *datastore.Key (DecodeKey(encoded string))
Get a specified key from the datastore (Get(c appengine.Context, key *Key, dst interface{}))
Now it's a really easy thing:
func home(w http.Response, r *http.Request) {
c := appengine.NewContext(r)
// Get the key from the URL
keyURL := r.FormValue("key")
// Decode the key
key, err := datastore.DecodeKey(keyURL)
if err != nil { // Couldn't decode the key
// Do some error handling
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Get the key and load it into "data"
var data Data
err = datastore.Get(c, key, data)
if err != nil { // Couldn't find the entity
// Do some error handling
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

Resources