This commit is contained in:
Joe
2026-01-16 15:49:34 +08:00
commit 550d3e1f42
380 changed files with 62024 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
//go:build docker
// +build docker
package feature
import (
"testing"
"github.com/goravel/framework/facades"
"github.com/stretchr/testify/assert"
)
func TestDBDrivers(t *testing.T) {
connections := []string{"postgres", "mysql", "sqlserver"}
for _, connection := range connections {
database, err := facades.Testing().Docker().Database(connection)
if err != nil {
panic(err)
}
if err := database.Build(); err != nil {
panic(err)
}
if err := database.Ready(); err != nil {
panic(err)
}
facades.Config().Add("database.default", connection)
facades.Config().Add("database.connections."+connection+".port", database.Config().Port)
facades.App().Refresh()
facades.Config().Add("database.default", "sqlite")
facades.App().Refresh()
assert.NoError(t, database.Shutdown())
}
}
+32
View File
@@ -0,0 +1,32 @@
package feature
import (
"testing"
"time"
"github.com/goravel/framework/contracts/event"
"github.com/goravel/framework/facades"
"github.com/stretchr/testify/assert"
"goravel/app/events"
)
func TestEvent(t *testing.T) {
// 测试事件调度是否成功
err1 := facades.Event().Job(&events.OrderShipped{}, []event.Arg{
{Type: "string", Value: "I'm OrderShipped"},
}).Dispatch()
assert.NoError(t, err1)
err2 := facades.Event().Job(&events.OrderCanceled{}, []event.Arg{
{Type: "string", Value: "I'm OrderCanceled"},
}).Dispatch()
assert.NoError(t, err2)
// 等待队列处理
time.Sleep(1 * time.Second)
// 注意:由于移除了全局测试变量,这里只验证事件调度是否成功
// 如果需要验证监听器执行结果,可以通过日志或其他方式验证
t.Log("Events dispatched successfully")
}
+30
View File
@@ -0,0 +1,30 @@
package feature
import (
"testing"
"github.com/stretchr/testify/suite"
"goravel/tests"
)
type ExampleTestSuite struct {
suite.Suite
tests.TestCase
}
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
}
// SetupTest will run before each test in the suite.
func (s *ExampleTestSuite) SetupTest() {
}
// TearDownTest will run after each test in the suite.
func (s *ExampleTestSuite) TearDownTest() {
}
func (s *ExampleTestSuite) TestIndex() {
s.True(true)
}
+158
View File
@@ -0,0 +1,158 @@
package feature
import (
"context"
"fmt"
"goravel/tests"
"os"
"testing"
"github.com/goravel/framework/contracts/filesystem"
contractsdocker "github.com/goravel/framework/contracts/testing/docker"
"github.com/goravel/framework/facades"
supportdocker "github.com/goravel/framework/support/docker"
testingdocker "github.com/goravel/framework/testing/docker"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/stretchr/testify/suite"
)
type FilesystemTestSuite struct {
suite.Suite
tests.TestCase
minioDocker contractsdocker.ImageDriver
drivers []string
}
func TestFilesystemTestSuite(t *testing.T) {
suite.Run(t, &FilesystemTestSuite{})
}
func (s *FilesystemTestSuite) SetupSuite() {
s.drivers = []string{
"",
"local",
"public",
}
if os.Getenv("AWS_ACCESS_KEY_ID") != "" {
s.drivers = append(s.drivers, "s3")
}
if os.Getenv("ALIYUN_ACCESS_KEY_ID") != "" {
s.drivers = append(s.drivers, "oss")
}
if os.Getenv("TENCENT_ACCESS_KEY_ID") != "" {
s.drivers = append(s.drivers, "cos")
}
if os.Getenv("MINIO_ACCESS_KEY_ID") != "" {
s.drivers = append(s.drivers, "minio")
s.minioDocker = initMinio()
}
fmt.Printf("testing filesystem drivers: %v\n", s.drivers)
}
func (s *FilesystemTestSuite) SetupTest() {
}
func (s *FilesystemTestSuite) TearDownSuite() {
if s.minioDocker != nil {
s.NoError(s.minioDocker.Shutdown())
}
}
func (s *FilesystemTestSuite) TestPutAndGet() {
for _, driver := range s.drivers {
var storage filesystem.Driver
if driver == "" {
storage = facades.Storage()
} else {
storage = facades.Storage().Disk(driver)
}
s.NoError(storage.Put("test.txt", "test"))
content, err := storage.Get("test.txt")
s.NoError(err)
s.Equal("test", content)
s.NoError(storage.Delete("test.txt"))
}
}
func initMinio() contractsdocker.ImageDriver {
minioAccessKey := os.Getenv("MINIO_ACCESS_KEY_ID")
minioSecretKey := os.Getenv("MINIO_ACCESS_KEY_SECRET")
minioBucket := os.Getenv("MINIO_BUCKET")
docker := testingdocker.NewImageDriver(contractsdocker.Image{
Repository: "minio/minio",
Tag: "latest",
Cmd: []string{"server", "/data"},
Env: []string{
"MINIO_ACCESS_KEY=" + minioAccessKey,
"MINIO_SECRET_KEY=" + minioSecretKey,
},
ExposedPorts: []string{
"9000",
},
})
err := docker.Build()
if err != nil {
panic(err)
}
config := docker.Config()
endpoint := fmt.Sprintf("127.0.0.1:%s", supportdocker.ExposedPort(config.ExposedPorts, "9000"))
facades.Config().Add("filesystems.disks.minio.endpoint", endpoint)
facades.Config().Add("filesystems.disks.minio.url", fmt.Sprintf("http://%s/%s", endpoint, minioBucket))
if err := docker.Ready(func() error {
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(minioAccessKey, minioSecretKey, ""),
})
if err != nil {
return err
}
if err := client.MakeBucket(context.Background(), minioBucket, minio.MakeBucketOptions{}); err != nil {
return err
}
policy := `{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Effect": "Allow",
"Principal": "*",
"Resource": [
"arn:aws:s3:::` + minioBucket + `/*"
]
},
{
"Action": [
"s3:ListBucket"
],
"Effect": "Allow",
"Principal": "*",
"Resource": [
"arn:aws:s3:::` + minioBucket + `"
]
}
]
}`
if err := client.SetBucketPolicy(context.Background(), minioBucket, policy); err != nil {
return err
}
return nil
}); err != nil {
panic(err)
}
return docker
}
+194
View File
@@ -0,0 +1,194 @@
package feature
import (
"fmt"
"strings"
"testing"
contractshttp "github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/http"
"github.com/stretchr/testify/suite"
"goravel/tests"
)
type HttpTestSuite struct {
suite.Suite
tests.TestCase
}
func TestHttpTestSuite(t *testing.T) {
suite.Run(t, &HttpTestSuite{})
}
func (s *HttpTestSuite) SetupSuite() {
}
// SetupTest will run before each test in the suite.
func (s *HttpTestSuite) SetupTest() {
s.RefreshDatabase()
}
// TearDownTest will run after each test in the suite.
func (s *HttpTestSuite) TearDownTest() {
}
func (s *HttpTestSuite) TestBindQuery() {
resp, err := s.Http(s.T()).Get("/bind-query?name=Goravel")
s.Require().NoError(err)
resp.AssertSuccessful()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal("{\"name\":\"Goravel\"}", content)
}
func (s *HttpTestSuite) TestFallback() {
resp, err := s.Http(s.T()).Get("/lang")
s.Require().NoError(err)
resp.AssertSuccessful()
resp, err = s.Http(s.T()).Get("/not-found")
s.Require().NoError(err)
resp.AssertNotFound()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal("fallback", content)
}
func (s *HttpTestSuite) TestFiles() {
body, err := http.NewBody().SetFiles(map[string][]string{
"files": []string{"lang/cn.json", "lang/en.json"},
}).Build()
s.Require().NoError(err)
resp, err := s.Http(s.T()).WithHeader("Content-Type", body.ContentType()).Post("/files", body.Reader())
s.Require().NoError(err)
resp.AssertSuccessful()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal("{\"files\":[\"cn.json\",\"en.json\"]}", content)
}
func (s *HttpTestSuite) TestInputMap() {
body, err := http.NewBody().SetField("test", map[string]any{"key1": "value1", "key2": "value2"}).Build()
s.Require().NoError(err)
resp, err := s.Http(s.T()).Post("/input-map", body.Reader())
s.Require().NoError(err)
resp.AssertSuccessful()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal("{\"test\":{\"key1\":\"value1\",\"key2\":\"value2\"}}", content)
}
func (s *HttpTestSuite) TestInputMapArray() {
body, err := http.NewBody().SetField("test", []map[string]any{{"key1": "value1", "key2": "value2"}, {"key3": "value3", "key4": "value4"}}).Build()
s.Require().NoError(err)
resp, err := s.Http(s.T()).Post("/input-map-array", body.Reader())
s.Require().NoError(err)
resp.AssertSuccessful()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal("{\"test\":[{\"key1\":\"value1\",\"key2\":\"value2\"},{\"key3\":\"value3\",\"key4\":\"value4\"}]}", content)
}
func (s *HttpTestSuite) TestLang() {
tests := []struct {
name string
lang string
expectResponse map[string]any
}{
{
name: "use default lang",
expectResponse: map[string]any{"current_locale": "en", "fallback": "Goravel 是一个基于 Go 语言的 Web 开发框架", "name": "Goravel Framework"},
},
{
name: "lang is cn",
lang: "cn",
expectResponse: map[string]any{"current_locale": "cn", "fallback": "Goravel 是一个基于 Go 语言的 Web 开发框架", "name": "Goravel 框架"},
},
{
name: "lang is fs",
lang: "fs",
expectResponse: map[string]any{"current_locale": "fs", "fallback": "Goravel 是一个基于 Go 语言的 Web 开发框架", "name": "fs name"},
},
}
for _, test := range tests {
s.Run(test.name, func() {
resp, err := s.Http(s.T()).Get(fmt.Sprintf("/lang?lang=%s", test.lang))
s.NoError(err)
resp.AssertSuccessful()
resp.AssertJson(test.expectResponse)
})
}
}
func (s *HttpTestSuite) TestPanic() {
resp, err := s.Http(s.T()).Get("/panic")
s.Require().NoError(err)
resp.AssertInternalServerError()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal("recover", content)
}
func (s *HttpTestSuite) TestStream() {
resp, err := s.Http(s.T()).Get("/stream")
s.Require().NoError(err)
resp.AssertSuccessful()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal("a\nb\nc\n", content)
}
func (s *HttpTestSuite) TestThrottle() {
resp, err := s.Http(s.T()).Get("/throttle")
s.Require().NoError(err)
resp.AssertSuccessful()
resp, err = s.Http(s.T()).Get("/throttle")
s.Require().NoError(err)
resp.AssertSuccessful()
resp, err = s.Http(s.T()).Get("/throttle")
s.Require().NoError(err)
resp.AssertTooManyRequests()
}
func (s *HttpTestSuite) TestTimeout() {
resp, err := s.Http(s.T()).Get("/timeout")
s.Require().NoError(err)
resp.AssertStatus(contractshttp.StatusRequestTimeout)
}
func (s *HttpTestSuite) TestUrl() {
resp, err := s.Http(s.T()).Get("/url/get/1?a=1&b=2")
s.Require().NoError(err)
resp.AssertSuccessful()
content, err := resp.Content()
s.Require().NoError(err)
s.Equal(`{"full_url":"http://example.com/url/get/1?a=1\u0026b=2","info":{"handler":"goravel/routes.Api.func11.1","method":"GET","name":"url.get","path":"/url/get/{id}"},"info1":{"handler":"goravel/routes.Api.func11.1","method":"GET|HEAD","name":"url.get","path":"/url/get/{id}"},"method":"GET","name":"url.get","origin_path":"/url/get/{id}","path":"/url/get/1","url":"/url/get/1?a=1\u0026b=2"}`, content)
resp, err = s.Http(s.T()).Post("/url/post/1?a=1&b=2", strings.NewReader("{\"name\":\"Goravel\"}"))
s.Require().NoError(err)
resp.AssertSuccessful()
content, err = resp.Content()
s.Require().NoError(err)
s.Equal(`{"full_url":"http://example.com/url/post/1?a=1\u0026b=2","info":{"handler":"goravel/routes.Api.func11.2","method":"POST","name":"url.post","path":"/url/post/{id}"},"info1":{"handler":"goravel/routes.Api.func11.2","method":"POST","name":"url.post","path":"/url/post/{id}"},"method":"POST","name":"url.post","origin_path":"/url/post/{id}","path":"/url/post/1","url":"/url/post/1?a=1\u0026b=2"}`, content)
}
+4
View File
@@ -0,0 +1,4 @@
{
"name": "Goravel 框架",
"description": "Goravel 是一个基于 Go 语言的 Web 开发框架"
}
+3
View File
@@ -0,0 +1,3 @@
{
"name": "Goravel Framework"
}
+60
View File
@@ -0,0 +1,60 @@
//go:build docker
// +build docker
package feature
import (
"os"
"testing"
"time"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/file"
)
func TestMain(m *testing.M) {
database, err := facades.Testing().Docker().Database()
if err != nil {
panic(err)
}
if err := database.Build(); err != nil {
panic(err)
}
if err := database.Migrate(); err != nil {
panic(err)
}
cache, err := facades.Testing().Docker().Cache("redis")
if err != nil {
panic(err)
}
if err := cache.Build(); err != nil {
panic(err)
}
if err := cache.Ready(); err != nil {
panic(err)
}
facades.Config().Add("database.redis.default.port", cache.Config().Port)
go func() {
if err := facades.Route().Run(); err != nil {
facades.Log().Errorf("Route run error: %v", err)
}
}()
time.Sleep(1 * time.Second)
exit := m.Run()
if err := file.Remove("storage"); err != nil {
panic(err)
}
if err := database.Shutdown(); err != nil {
panic(err)
}
if err := cache.Shutdown(); err != nil {
panic(err)
}
os.Exit(exit)
}
+258
View File
@@ -0,0 +1,258 @@
package feature
import (
"errors"
"testing"
"time"
contractsqueue "github.com/goravel/framework/contracts/queue"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/queue/utils"
"github.com/goravel/framework/support/carbon"
"github.com/stretchr/testify/suite"
"goravel/app/jobs"
"goravel/tests"
)
type QueueTestSuite struct {
suite.Suite
tests.TestCase
}
func TestQueueTestSuite(t *testing.T) {
suite.Run(t, &QueueTestSuite{})
}
// SetupTest will run before each test in the suite.
func (s *QueueTestSuite) SetupTest() {
jobs.TestResult = nil
jobs.TestErrResult = nil
}
// TearDownTest will run after each test in the suite.
func (s *QueueTestSuite) TearDownTest() {
}
func (s *QueueTestSuite) TestDispatch() {
s.NoError(facades.Queue().Job(&jobs.Test{}, testQueueArgs).Dispatch())
time.Sleep(1 * time.Second)
s.Equal(utils.ConvertArgs(testQueueArgs), jobs.TestResult)
}
func (s *QueueTestSuite) TestDispatchWithDelay() {
s.NoError(facades.Queue().Job(&jobs.Test{}, testQueueArgs).Delay(time.Now().Add(1 * time.Second)).Dispatch())
time.Sleep(2 * time.Second)
s.Equal(utils.ConvertArgs(testQueueArgs), jobs.TestResult)
}
func (s *QueueTestSuite) TestDispatchChain() {
s.NoError(facades.Queue().Chain([]contractsqueue.ChainJob{
{
Job: &jobs.Test{},
Args: testQueueArgs,
},
{
Job: &jobs.Test{},
Args: testQueueArgs,
},
}).Dispatch())
time.Sleep(1 * time.Second)
var args []any
for i := 0; i < 2; i++ {
args = append(args, utils.ConvertArgs(testQueueArgs)...)
}
s.Equal(args, jobs.TestResult)
}
func (s *QueueTestSuite) TestDispatchWithQueue() {
s.NoError(facades.Queue().Job(&jobs.Test{}, testQueueArgs).OnQueue("test").Dispatch())
time.Sleep(1 * time.Second)
s.Equal(utils.ConvertArgs(testQueueArgs), jobs.TestResult)
}
func (s *QueueTestSuite) TestDispatchWithConnectionAndQueue() {
if facades.Config().GetString("queue.default") == "sync" {
s.T().Skip("skip test due to only for redis")
}
s.NoError(facades.Queue().Job(&jobs.Test{}, testQueueArgs).OnConnection("redis1").OnQueue("test").Dispatch())
time.Sleep(1 * time.Second)
s.Equal(utils.ConvertArgs(testQueueArgs), jobs.TestResult)
}
func (s *QueueTestSuite) TestSyncFailedJob() {
if facades.Config().GetString("queue.default") != "sync" {
s.T().Skip("skip test due to only for sync")
}
s.Equal(errors.New("test error"), facades.Queue().Job(&jobs.TestErr{}).Dispatch())
}
func (s *QueueTestSuite) TestFailedJobAndRetry() {
if facades.Config().GetString("queue.default") == "sync" {
s.T().Skip("skip test due to only for non-sync")
}
carbon.SetTestNow(carbon.Now())
defer carbon.ClearTestNow()
testErr := &jobs.TestErr{}
s.NoError(facades.Queue().Job(testErr, []contractsqueue.Arg{
{
Type: "string",
Value: "test",
},
}).Dispatch())
time.Sleep(1 * time.Second)
s.Equal([]any{"test"}, jobs.TestErrResult)
failedJobs, err := facades.Queue().Failer().All()
s.Require().NoError(err)
if facades.Config().GetString("queue.default") != "machinery" {
s.Require().Equal(1, len(failedJobs))
s.Equal("default", failedJobs[0].Queue())
s.Equal(facades.Config().GetString("queue.default"), failedJobs[0].Connection())
s.Equal(carbon.NewDateTime(carbon.Now()), failedJobs[0].FailedAt())
s.Equal(testErr.Signature(), failedJobs[0].Signature())
s.NotEmpty(failedJobs[0].UUID())
s.NoError(facades.Artisan().Call("queue:retry"))
time.Sleep(1 * time.Second)
s.Equal([]any{"test", "test"}, jobs.TestErrResult)
}
}
var (
testQueueArgs = []contractsqueue.Arg{
{
Type: "bool",
Value: true,
},
{
Type: "int",
Value: 1,
},
{
Type: "int8",
Value: int8(1),
},
{
Type: "int16",
Value: int16(1),
},
{
Type: "int32",
Value: int32(1),
},
{
Type: "int64",
Value: int64(1),
},
{
Type: "uint",
Value: uint(1),
},
{
Type: "uint8",
Value: uint8(1),
},
{
Type: "uint16",
Value: uint16(1),
},
{
Type: "uint32",
Value: uint32(1),
},
{
Type: "uint64",
Value: uint64(1),
},
{
Type: "float32",
Value: float32(1.1),
},
{
Type: "float64",
Value: float64(1.2),
},
{
Type: "string",
Value: "test",
},
{
Type: "[]bool",
Value: []bool{true, false},
},
{
Type: "[]int",
Value: []int{1, 2, 3},
},
{
Type: "[]int8",
Value: []int8{1, 2, 3},
},
{
Type: "[]int16",
Value: []int16{1, 2, 3},
},
{
Type: "[]int32",
Value: []int32{1, 2, 3},
},
{
Type: "[]int64",
Value: []int64{1, 2, 3},
},
{
Type: "[]uint",
Value: []uint{1, 2, 3},
},
{
Type: "[]uint8",
Value: []uint8{1, 2, 3},
},
{
Type: "[]uint16",
Value: []uint16{1, 2, 3},
},
{
Type: "[]uint32",
Value: []uint32{1, 2, 3},
},
{
Type: "[]uint64",
Value: []uint64{1, 2, 3},
},
{
Type: "[]float32",
Value: []float32{1.1, 1.2, 1.3},
},
{
Type: "[]float64",
Value: []float64{1.1, 1.2, 1.3},
},
{
Type: "[]string",
Value: []string{"test", "test2", "test3"},
},
}
)
+81
View File
@@ -0,0 +1,81 @@
package feature
import (
"testing"
"github.com/goravel/framework/contracts/queue"
"github.com/goravel/framework/facades"
"github.com/stretchr/testify/suite"
)
func TestRedisDriver(t *testing.T) {
facades.Config().Add("cache.default", "redis")
facades.Config().Add("queue.default", "redis")
facades.App().Refresh()
go func() {
if err := facades.Queue().Worker().Run(); err != nil {
facades.Log().Errorf("Queue run error: %v", err)
}
}()
go func() {
if err := facades.Queue().Worker(queue.Args{
Queue: "test",
}).Run(); err != nil {
facades.Log().Errorf("Queue run error: %v", err)
}
}()
go func() {
if err := facades.Queue().Worker(queue.Args{
Connection: "redis1",
Queue: "test",
}).Run(); err != nil {
facades.Log().Errorf("Queue run error: %v", err)
}
}()
suite.Run(t, &HttpTestSuite{})
suite.Run(t, &QueueTestSuite{})
facades.Config().Add("cache.default", "memory")
facades.Config().Add("queue.default", "sync")
facades.App().Refresh()
}
func TestMachineryDriver(t *testing.T) {
facades.Config().Add("cache.default", "redis")
facades.Config().Add("queue.default", "machinery")
facades.App().Refresh()
go func() {
if err := facades.Queue().Worker().Run(); err != nil {
facades.Log().Errorf("Queue run error: %v", err)
}
}()
go func() {
if err := facades.Queue().Worker(queue.Args{
Queue: "test",
Concurrent: 2,
}).Run(); err != nil {
facades.Log().Errorf("Queue run error: %v", err)
}
}()
go func() {
if err := facades.Queue().Worker(queue.Args{
Connection: "redis1",
Queue: "test",
}).Run(); err != nil {
facades.Log().Errorf("Queue run error: %v", err)
}
}()
suite.Run(t, &QueueTestSuite{})
facades.Config().Add("cache.default", "memory")
facades.Config().Add("queue.default", "sync")
facades.App().Refresh()
}