func (g *Db) Save(jobID int, stats []constants.Statistics) (IDs []int, err error) {
	err = g.updateJobRecord(jobID, stats)
	if err != nil {
		return nil, err
	}
	IDs = []int{}
	for _, stat := range stats {
		resultID, err := g.saveStatistics(jobID, stat)
		if err != nil {
			return nil, err
		}
		IDs = append(IDs, resultID)
	}
	return IDs, nil
}
Writes a set of data.Statistics to the DB database and returns the row numbers
package db
import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/mercari/testdeck/service/config"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"
	"testing"
	"time"
	"github.com/pkg/errors"
	"github.com/mercari/testdeck/constants"
)
var HTTPClient *http.Client
const (
	EnvKeyJobName = "JOB_NAME"
	EnvKeyPodName = "POD_NAME"
)
const (
	Job = "job"
	JobUpdate = "jobUpdate"
	Statistic = "statistic"
	Timing = "timing"
	Status = "status"
)
var Endpoints = map[string]string{
	Job:       "/job",
	JobUpdate: "/job/:id",
	Statistic: "/result",
	Timing:    "/timing",
	Status:    "/status",
}
type ServerResponse struct {
	ID     int    `json:"id"`
	Status string `json:"status"`
	Error  string `json:"error"`
}
type Db struct {
	GcpProjectID string
}
func readFromConfig() *config.Env {
	env, err := config.ReadFromEnv()
	if err != nil {
		panic(errors.Wrap(err, "Could not read environment variables!"))
	}
	return env
}
func GuardProduction(t *testing.T) {
	url := readFromConfig().DbUrl
	if strings.Contains(strings.ToLower(url), "prod") {
		t.Fatalf("You are trying to run this test against what looks like a production database! Current DB URL: %s", url)
	}
}
func composeEndpoint(endpoint string) string {
	return readFromConfig().DbUrl + Endpoints[endpoint]
}
type MySQLTime struct {
	time.Time
}
func (t MySQLTime) MarshalJSON() ([]byte, error) {
	ts := t.UTC().Format("\"2006-01-02 15:04:05\"")
	return []byte(ts), nil
}
type job struct {
	ID           int           `json:"id"`
	GcpProjectID string        `json:"gcp_project_id"`
	JobName      string        `json:"job_name"`
	PodName      string        `json:"pod_name"`
	Finished     bool          `json:"finished"`
	Failed       bool          `json:"failed"`
	Start        MySQLTime     `json:"start_ts"`
	End          MySQLTime     `json:"end_ts"`
	Duration     time.Duration `json:"duration_ns"`
}
func newJob() *job {
	jb := &job{}
	jb.JobName = os.Getenv(EnvKeyJobName)
	jb.PodName = os.Getenv(EnvKeyPodName)
	jb.Start = MySQLTime{time.Now()}
	jb.End = jb.Start
	return jb
}
type jobUpdate struct {
	Finished bool          `json:"finished"`
	Failed   bool          `json:"failed"`
	Start    MySQLTime     `json:"start_ts"`
	End      MySQLTime     `json:"end_ts"`
	Duration time.Duration `json:"duration_ns"`
}
func (g *Db) SaveJobStart() (int, error) {
	jb := newJob()
	jb.GcpProjectID = g.GcpProjectID
	err := insertRestOperation(composeEndpoint(Job), jb)
	return jb.ID, err
}
func (g *Db) updateJobRecord(ID int, stats []constants.Statistics) error {
	update := jobUpdate{
		Failed:   false,
		Finished: true,
	}
	for _, stat := range stats {
		if stat.Failed {
			update.Failed = true
		}
		if update.Start.IsZero() || update.Start.After(stat.Start) {
			update.Start = MySQLTime{stat.Start}
		}
		if stat.End.After(update.End.Time) {
			update.End = MySQLTime{stat.End}
		}
	}
	update.Duration = update.End.Sub(update.Start.Time)
	resource := strings.Replace(composeEndpoint(JobUpdate), ":id", strconv.Itoa(ID), -1)
	return updateRestOperation(resource, update)
}
type result struct {
	ID           int           `json:"id"`
	JobID        int           `json:"job_id"`
	GcpProjectID string        `json:"gcp_project_id"`
	Name         string        `json:"test_name"`
	Failed       bool          `json:"failed"`
	Fatal        bool          `json:"fatal"`
	Start        MySQLTime     `json:"start_ts"`
	End          MySQLTime     `json:"end_ts"`
	Duration     time.Duration `json:"duration_ns"`
	Output       string        `json:"output_text"`
}
func newResultFrom(jobID int, stats constants.Statistics) *result {
	return &result{
		JobID:    jobID,
		Name:     stats.Name,
		Failed:   stats.Failed,
		Fatal:    stats.Fatal,
		Start:    MySQLTime{stats.Start},
		End:      MySQLTime{stats.End},
		Duration: stats.Duration,
		Output:   stats.Output,
	}
}