-
Notifications
You must be signed in to change notification settings - Fork 1
/
functest.go
227 lines (189 loc) · 7.39 KB
/
functest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Code generated by protoc-gen-gomeet-service. DO NOT EDIT.
// source: pb/profile.proto
package cmd
import (
"fmt"
"net"
"os"
"strings"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/gomeet/gomeet/utils/jwt"
"github.com/gomeet-examples/svc-profile/cmd/functest"
"github.com/gomeet-examples/svc-profile/models"
)
var (
useEmbeddedServer bool
useRandomPort bool
mysqlFunctestMigrate bool
// funcTestCmd represents the functest command
funcTestCmd = &cobra.Command{
Use: "functest",
Short: "Runs functional tests on the service",
Run: func(cmd *cobra.Command, args []string) {
runFunctionalTests()
},
}
)
func init() {
RootCmd.AddCommand(funcTestCmd)
// force debug mode
funcTestCmd.PersistentFlags().BoolVarP(&debugMode, "debug", "d", false, "Force debug mode")
// address flag (to serve all protocols on a single port)
funcTestCmd.PersistentFlags().StringVarP(&serverAddress, "address", "a", "localhost:13000", "Multiplexed gRPC/HTTP server address")
// gRPC address flag (to serve gRPC on a separate address)
funcTestCmd.PersistentFlags().StringVar(&grpcServerAddress, "grpc-address", "", "gRPC server address")
// HTTP/1.1 address flag (to serve HTTP on a separate address)
funcTestCmd.PersistentFlags().StringVar(&httpServerAddress, "http-address", "", "HTTP server address")
// CA certificate
funcTestCmd.PersistentFlags().StringVar(&caCertificate, "ca", "", "X.509 certificate of the Certificate Authority (required for gRPC TLS support)")
// gRPC client certificate
funcTestCmd.PersistentFlags().StringVar(&serverCertificate, "cert", "", "X.509 certificate (required for gRPC TLS support)")
// gRPC client private key
funcTestCmd.PersistentFlags().StringVar(&serverPrivateKey, "key", "", "RSA private key (required for gRPC TLS support)")
// JSON Web Token
funcTestCmd.PersistentFlags().StringVar(&jwtToken, "jwt", "", "JSON Web Token (external server only)")
// JWT secret signing key
funcTestCmd.PersistentFlags().StringVar(&jwtSecret, "jwt-secret", "", "JSON Web Token secret signing key (embedded server only)")
// request timeout
funcTestCmd.PersistentFlags().IntVar(&timeoutSeconds, "timeout", 5, "Request timeout in seconds")
// embedded server flag
funcTestCmd.PersistentFlags().BoolVarP(&useEmbeddedServer, "embed-server", "e", false, "Embed the server to test")
// random port server flag
funcTestCmd.PersistentFlags().BoolVar(&useRandomPort, "random-port", false, "Use a random port for the embedded server")
// Mysql data migration on start
funcTestCmd.PersistentFlags().BoolVar(&mysqlFunctestMigrate, "mysql-migrate", false, "Mysql data migration on start")
// Mysql data source name: https://github.com/go-sql-driver/mysql#dsn-data-source-name
funcTestCmd.PersistentFlags().StringVar(&mysqlDataSourceName, "mysql-dsn", "", "Mysql connection informations (USERNAME:PASSWORD@tcp(HOSTNAME:3306)/DBNAME)")
}
// getFreePort asks the kernel for a free open port that is ready to use.
func getFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
func runFunctionalTests() {
if useEmbeddedServer {
if debugMode {
log.SetLevel(log.DebugLevel)
} else {
// by default for embedded server only panic are logged
log.SetLevel(log.PanicLevel)
}
if useRandomPort {
freePort, err := getFreePort()
if err == nil {
serverAddress = fmt.Sprintf("localhost:%d", freePort)
grpcServerAddress = ""
httpServerAddress = ""
}
}
if grpcServerAddress != "" && httpServerAddress != "" {
go serveOnMultipleAddresses(grpcServerAddress, httpServerAddress)
} else {
go serveOnSingleAddress(serverAddress)
}
}
// if the embedded server is JWT-enabled, test clients require a valid token
if useEmbeddedServer && jwtSecret != "" {
var err error
jwtToken, err = jwt.Create(
"github.com/gomeet/gomeet",
secretSigningKey,
tokenLifetimeHours,
subjectID,
jwt.Claims{},
)
if err != nil {
fmt.Printf("failed to create JWT : %v\n", err)
os.Exit(1)
}
}
// initialize the mysql database schema
if mysqlDataSourceName != "" && mysqlFunctestMigrate {
if strings.Contains(mysqlDataSourceName, "?") {
fmt.Println("database connection error: data source name cannot contain options")
os.Exit(1)
}
mysqlDSN := fmt.Sprintf("%s?charset=utf8&parseTime=True", mysqlDataSourceName)
mysqlDB, err := gorm.Open("mysql", mysqlDSN)
if err != nil {
fmt.Printf("Database connection error (DSN \"%s\"): %v\n", mysqlDSN, err)
os.Exit(1)
}
// ping the mysql database server
err = mysqlDB.DB().Ping()
if err != nil {
fmt.Printf("Ping database connection error (DSN \"%s\"): %v\n", mysqlDSN, err)
os.Exit(1)
}
err = models.MigrateSchema(mysqlDataSourceName)
if err != nil {
fmt.Printf("Mysql schema migration error: %s\n", err)
os.Exit(1)
}
}
testConfig := functest.FunctionalTestConfig{
ServerAddress: serverAddress,
GrpcServerAddress: grpcServerAddress,
HttpServerAddress: httpServerAddress,
CaCertificate: caCertificate,
ClientCertificate: serverCertificate,
ClientPrivateKey: serverPrivateKey,
TimeoutSeconds: timeoutSeconds,
JsonWebToken: jwtToken,
MysqlDataSourceName: mysqlDataSourceName,
}
failures := runFunctionalTestSession(testConfig)
if len(failures) == 0 {
fmt.Println("PASS")
fmt.Println("ok\tfunctest is ok")
os.Exit(0)
} else {
fmt.Printf("Test failures:\n")
for idx, failure := range failures {
fmt.Printf("%d) %s: %s\n", idx+1, failure.Procedure, failure.Message)
}
os.Exit(1)
}
}
func appendFailures(acc []functest.TestFailure, newSlice []functest.TestFailure) []functest.TestFailure {
for _, failure := range newSlice {
acc = append(acc, failure)
}
return acc
}
func runFunctionalTestSession(config functest.FunctionalTestConfig) []functest.TestFailure {
var failures []functest.TestFailure
// gRPC services test
failures = appendFailures(failures, functest.TestVersion(config))
failures = appendFailures(failures, functest.TestHttpVersion(config))
failures = appendFailures(failures, functest.TestServicesStatus(config))
failures = appendFailures(failures, functest.TestHttpServicesStatus(config))
failures = appendFailures(failures, functest.TestCreate(config))
failures = appendFailures(failures, functest.TestHttpCreate(config))
failures = appendFailures(failures, functest.TestRead(config))
failures = appendFailures(failures, functest.TestHttpRead(config))
failures = appendFailures(failures, functest.TestList(config))
failures = appendFailures(failures, functest.TestHttpList(config))
failures = appendFailures(failures, functest.TestUpdate(config))
failures = appendFailures(failures, functest.TestHttpUpdate(config))
failures = appendFailures(failures, functest.TestSoftDelete(config))
failures = appendFailures(failures, functest.TestHttpSoftDelete(config))
failures = appendFailures(failures, functest.TestHardDelete(config))
failures = appendFailures(failures, functest.TestHttpHardDelete(config))
// Extra http handler
failures = appendFailures(failures, functest.TestHttpStatus(config))
failures = appendFailures(failures, functest.TestHttpMetrics(config))
failures = appendFailures(failures, functest.TestHttpSwagger(config))
return failures
}