mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-09-30 03:22:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41aa9b189c | ||
|
|
ed9cc2f33c | ||
|
|
bada2338f7 | ||
|
|
3b6fcf265a | ||
|
|
fb78a39161 | ||
|
|
9436efb7fd | ||
|
|
05556e7cbc | ||
|
|
2862119c1f | ||
|
|
eaf121ead7 | ||
|
|
aabe820e35 | ||
|
|
80d65a198b | ||
|
|
3013e0299a | ||
|
|
6fd2e7ab0f | ||
|
|
a44a73a17c | ||
|
|
bf30af393e | ||
|
|
f0410a7625 | ||
|
|
ba56623245 | ||
|
|
702b4aa1c2 | ||
|
|
3f7db19fdd | ||
|
|
9855397a22 | ||
|
|
d9b219d64f | ||
|
|
c642a860ca | ||
|
|
b2b792b763 | ||
|
|
fc18e69183 | ||
|
|
fa65038fc1 | ||
|
|
9419d1928a | ||
|
|
c9bc2f07aa | ||
|
|
28fc186f5c | ||
|
|
cdb539dcc8 | ||
|
|
af7c6d8d9b | ||
|
|
cd2fc536ca | ||
|
|
2a28f6ff33 | ||
|
|
036c0da05f | ||
|
|
d795a6671b | ||
|
|
8cff94f27c | ||
|
|
931f6bc0cb | ||
|
|
2e3ae1b60a | ||
|
|
6155e6426a | ||
|
|
0f95a11fc1 | ||
|
|
eb695cc6d3 | ||
|
|
28d15e86eb | ||
|
|
b033109654 | ||
|
|
d0352aa3f9 | ||
|
|
3b7d0e84f6 | ||
|
|
592b7e85c6 | ||
|
|
3792d44c35 | ||
|
|
a021fcaa75 | ||
|
|
d123e19e61 | ||
|
|
982f876a93 | ||
|
|
1fcc2d8683 | ||
|
|
4f2492290e | ||
|
|
8f625daa2f | ||
|
|
0599955676 | ||
|
|
97a8409a65 | ||
|
|
422eb30797 | ||
|
|
705d7f48e7 | ||
|
|
e73b3a32d2 | ||
|
|
370862fa2e | ||
|
|
6b3780c630 | ||
|
|
c807f66c59 | ||
|
|
5f5f9ca426 | ||
|
|
4e91be6d74 |
No files matched your search
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: pocketbase
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://www.paypal.com/donate/?hosted_button_id=4DVXNL4B8WT98']
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '>=1.21.3'
|
||||
go-version: '>=1.21.6'
|
||||
|
||||
# This step usually is not needed because the /ui/dist is pregenerated locally
|
||||
# but its here to ensure that each release embeds the latest admin ui artifacts.
|
||||
|
||||
+2
-2
@@ -51,8 +51,8 @@ archives:
|
||||
builds: [build_noncgo]
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE*
|
||||
- CHANGELOG*
|
||||
- LICENSE.md
|
||||
- CHANGELOG.md
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
@@ -1,3 +1,84 @@
|
||||
## v0.21.2
|
||||
|
||||
- Fixed `@request.auth.*` initialization side-effect which caused the current authenticated user email to not being returned in the user auth response ([#2173](https://github.com/pocketbase/pocketbase/issues/2173#issuecomment-1932332038)).
|
||||
_The current authenticated user email should be accessible always no matter of the `emailVisibility` state._
|
||||
|
||||
- Fixed `RecordUpsert.RemoveFiles` godoc example.
|
||||
|
||||
- Bumped to `NumCPU()+2` the `thumbGenSem` limit as some users reported that it was too restrictive.
|
||||
|
||||
|
||||
## v0.21.1
|
||||
|
||||
- Small fix for the Admin UI related to the _Settings > Sync_ menu not being visible even when the "Hide controls" toggle is off.
|
||||
|
||||
|
||||
## v0.21.0
|
||||
|
||||
- Added Bitbucket OAuth2 provider ([#3948](https://github.com/pocketbase/pocketbase/pull/3948); thanks @aabajyan).
|
||||
|
||||
- Mark user as verified on confirm password reset ([#4066](https://github.com/pocketbase/pocketbase/issues/4066)).
|
||||
_If the user email has changed after issuing the reset token (eg. updated by an admin), then the `verified` user state remains unchanged._
|
||||
|
||||
- Added support for loading a serialized json payload for `multipart/form-data` requests using the special `@jsonPayload` key.
|
||||
_This is intended to be used primarily by the SDKs to resolve [js-sdk#274](https://github.com/pocketbase/js-sdk/issues/274)._
|
||||
|
||||
- Added graceful OAuth2 redirect error handling ([#4177](https://github.com/pocketbase/pocketbase/issues/4177)).
|
||||
_Previously on redirect error we were returning directly a standard json error response. Now on redirect error we'll redirect to a generic OAuth2 failure screen (similar to the success one) and will attempt to auto close the OAuth2 popup._
|
||||
_The SDKs are also updated to handle the OAuth2 redirect error and it will be returned as Promise rejection of the `authWithOAuth2()` call._
|
||||
|
||||
- Exposed `$apis.gzip()` and `$apis.bodyLimit(bytes)` middlewares to the JSVM.
|
||||
|
||||
- Added `TestMailer.SentMessages` field that holds all sent test app emails until cleanup.
|
||||
|
||||
- Optimized the cascade delete of records with multiple `relation` fields.
|
||||
|
||||
- Updated the `serve` and `admin` commands error reporting.
|
||||
|
||||
- Minor Admin UI improvements (reduced the min table row height, added option to duplicate fields, added new TinyMCE codesample plugin languages, hide the collection sync settings when the `Settings.Meta.HideControls` is enabled, etc.)
|
||||
|
||||
|
||||
## v0.20.7
|
||||
|
||||
- Fixed the Admin UI auto indexes update when renaming fields with a common prefix ([#4160](https://github.com/pocketbase/pocketbase/issues/4160)).
|
||||
|
||||
|
||||
## v0.20.6
|
||||
|
||||
- Fixed JSVM types generation for functions with omitted arg types ([#4145](https://github.com/pocketbase/pocketbase/issues/4145)).
|
||||
|
||||
- Updated Go deps.
|
||||
|
||||
|
||||
## v0.20.5
|
||||
|
||||
- Minor CSS fix for the Admin UI to prevent the searchbar within a popup from expanding too much and pushing the controls out of the visible area ([#4079](https://github.com/pocketbase/pocketbase/issues/4079#issuecomment-1876994116)).
|
||||
|
||||
|
||||
## v0.20.4
|
||||
|
||||
- Small fix for a regression introduced with the recent `json` field changes that was causing View collection column expressions recognized as `json` to fail to resolve ([#4072](https://github.com/pocketbase/pocketbase/issues/4072)).
|
||||
|
||||
|
||||
## v0.20.3
|
||||
|
||||
- Fixed the `json` field query comparisons to work correctly with plain JSON values like `null`, `bool` `number`, etc. ([#4068](https://github.com/pocketbase/pocketbase/issues/4068)).
|
||||
Since there are plans in the future to allow custom SQLite builds and also in some situations it may be useful to be able to distinguish `NULL` from `''`,
|
||||
for the `json` fields (and for any other future non-standard field) we no longer apply `COALESCE` by default, aka.:
|
||||
```
|
||||
Dataset:
|
||||
1) data: json(null)
|
||||
2) data: json('')
|
||||
|
||||
For the filter "data = null" only 1) will resolve to TRUE.
|
||||
For the filter "data = ''" only 2) will resolve to TRUE.
|
||||
```
|
||||
|
||||
- Minor Go tests improvements
|
||||
- Sorted the record cascade delete references to ensure that the delete operation will preserve the order of the fired events when running the tests.
|
||||
- Marked some of the tests as safe for parallel execution to speed up a little the GitHub action build times.
|
||||
|
||||
|
||||
## v0.20.2
|
||||
|
||||
- Added `sleep(milliseconds)` JSVM binding.
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ This document describes how to prepare a PR for a change in the main repository.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.19+ (for making changes in the Go code)
|
||||
- Node 16+ (for making changes in the Admin UI)
|
||||
- Go 1.21+ (for making changes in the Go code)
|
||||
- Node 18+ (for making changes in the Admin UI)
|
||||
|
||||
If you haven't already, you can fork the main repository and clone your fork so that you can work locally:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/pocketbase/pocketbase/actions/workflows/release.yaml" target="_blank" rel="noopener"><img src="https://github.com/pocketbase/pocketbase/actions/workflows/release.yaml/badge.svg" alt="build" /></a>
|
||||
<a href="https://github.com/pocketbase/pocketbase/releases" target="_blank" rel="noopener"><img src="https://img.shields.io/github/release/pocketbase/pocketbase.svg" alt="Latest releases" /></a>
|
||||
<a href="https://pkg.go.dev/github.com/pocketbase/pocketbase" target="_blank" rel="noopener"><img src="https://godoc.org/github.com/ganigeorgiev/fexpr?status.svg" alt="Go package documentation" /></a>
|
||||
<a href="https://pkg.go.dev/github.com/pocketbase/pocketbase" target="_blank" rel="noopener"><img src="https://godoc.org/github.com/pocketbase/pocketbase?status.svg" alt="Go package documentation" /></a>
|
||||
</p>
|
||||
|
||||
[PocketBase](https://pocketbase.io) is an open source Go backend, consisting of:
|
||||
@@ -144,7 +144,6 @@ You could help continuing its development by:
|
||||
|
||||
- [Contribute to the source code](CONTRIBUTING.md)
|
||||
- [Suggest new features and report issues](https://github.com/pocketbase/pocketbase/issues)
|
||||
- [Donate a small amount](https://pocketbase.io/support-us)
|
||||
|
||||
PRs for new OAuth2 providers, bug fixes, code optimizations and documentation improvements are more than welcome.
|
||||
|
||||
@@ -153,7 +152,3 @@ PocketBase has a [roadmap](https://github.com/orgs/pocketbase/projects/2) and I
|
||||
|
||||
Don't get upset if I close your PR, even if it is well executed and tested. This doesn't mean that it will never be merged.
|
||||
Later we can always refer to it and/or take pieces of your implementation when the time comes to work on the issue (don't worry you'll be credited in the release notes).
|
||||
|
||||
> [!NOTE]
|
||||
> PocketBase was initially created to serve as a new backend for my other open source project - [Presentator](https://presentator.io) (see [#183](https://github.com/presentator/presentator/issues/183)),
|
||||
> so all feature requests will be first aligned with what we need for Presentator v3.
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminAuthWithPassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "empty data",
|
||||
@@ -119,6 +121,8 @@ func TestAdminAuthWithPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminRequestPasswordReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "empty data",
|
||||
@@ -188,6 +192,8 @@ func TestAdminRequestPasswordReset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminConfirmPasswordReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "empty data",
|
||||
@@ -277,6 +283,8 @@ func TestAdminConfirmPasswordReset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminRefresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -350,6 +358,8 @@ func TestAdminRefresh(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminsList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -453,6 +463,8 @@ func TestAdminsList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminView(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -508,6 +520,8 @@ func TestAdminView(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -603,6 +617,8 @@ func TestAdminDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized (while having at least 1 existing admin)",
|
||||
@@ -757,6 +773,8 @@ func TestAdminCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNewApiErrorWithRawData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := apis.NewApiError(
|
||||
300,
|
||||
"message_test",
|
||||
@@ -33,6 +35,8 @@ func TestNewApiErrorWithRawData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewApiErrorWithValidationData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := apis.NewApiError(
|
||||
300,
|
||||
"message_test",
|
||||
@@ -66,6 +70,8 @@ func TestNewApiErrorWithValidationData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewNotFoundError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
message string
|
||||
data any
|
||||
@@ -87,6 +93,8 @@ func TestNewNotFoundError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewBadRequestError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
message string
|
||||
data any
|
||||
@@ -108,6 +116,8 @@ func TestNewBadRequestError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewForbiddenError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
message string
|
||||
data any
|
||||
@@ -129,6 +139,8 @@ func TestNewForbiddenError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewUnauthorizedError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
message string
|
||||
data any
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
)
|
||||
|
||||
func TestBackupsList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -84,6 +86,8 @@ func TestBackupsList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupsCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -198,6 +202,8 @@ func TestBackupsCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupsUpload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// create dummy form data bodies
|
||||
type body struct {
|
||||
buffer io.Reader
|
||||
@@ -330,6 +336,8 @@ func TestBackupsUpload(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupsDownload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -485,6 +493,8 @@ func TestBackupsDownload(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupsDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noTestBackupFilesChanges := func(t *testing.T, app *tests.TestApp) {
|
||||
files, err := getBackupFiles(app)
|
||||
if err != nil {
|
||||
@@ -645,6 +655,8 @@ func TestBackupsDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupsRestore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ const trailedAdminPath = "/_/"
|
||||
func InitApi(app core.App) (*echo.Echo, error) {
|
||||
e := echo.New()
|
||||
e.Debug = false
|
||||
e.Binder = &rest.MultiBinder{}
|
||||
e.JSONSerializer = &rest.Serializer{
|
||||
FieldsParam: fieldsQueryParam,
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func InitApi(app core.App) (*echo.Echo, error) {
|
||||
logRequest(app, c, apiErr)
|
||||
|
||||
if c.Response().Committed {
|
||||
return // already commited
|
||||
return // already committed
|
||||
}
|
||||
|
||||
event := new(core.ApiErrorEvent)
|
||||
|
||||
+61
-40
@@ -11,10 +11,13 @@ import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func Test404(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
@@ -53,6 +56,8 @@ func Test404(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCustomRoutesAndErrorsHandling(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "custom route",
|
||||
@@ -142,6 +147,8 @@ func TestCustomRoutesAndErrorsHandling(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveTrailingSlashMiddleware(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "non /api/* route (exact match)",
|
||||
@@ -214,13 +221,24 @@ func TestRemoveTrailingSlashMiddleware(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEagerRequestInfoCache(t *testing.T) {
|
||||
func TestMultiBinder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rawJson := `{"name":"test123"}`
|
||||
|
||||
formData, mp, err := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: rawJson,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "custom non-api group route",
|
||||
Name: "non-api group route",
|
||||
Method: "POST",
|
||||
Url: "/custom",
|
||||
Body: strings.NewReader(`{"name":"test123"}`),
|
||||
Body: strings.NewReader(rawJson),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: "POST",
|
||||
@@ -234,11 +252,10 @@ func TestEagerRequestInfoCache(t *testing.T) {
|
||||
return err
|
||||
}
|
||||
|
||||
// since the unknown method is not eager cache support
|
||||
// it should fail reading the json body twice
|
||||
// try to read the body again
|
||||
r := apis.RequestInfo(c)
|
||||
if v := cast.ToString(r.Data["name"]); v != "" {
|
||||
t.Fatalf("Expected empty request data body, got, %v", r.Data)
|
||||
if v := cast.ToString(r.Data["name"]); v != "test123" {
|
||||
t.Fatalf("Expected request data with name %q, got, %q", "test123", v)
|
||||
}
|
||||
|
||||
return c.NoContent(200)
|
||||
@@ -248,41 +265,10 @@ func TestEagerRequestInfoCache(t *testing.T) {
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
{
|
||||
Name: "api group route with unsupported eager cache request method",
|
||||
Name: "api group route",
|
||||
Method: "GET",
|
||||
Url: "/api/admins",
|
||||
Body: strings.NewReader(`{"name":"test123"}`),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// it is not important whether the route handler return an error since
|
||||
// we just need to ensure that the eagerRequestInfoCache was registered
|
||||
next(c)
|
||||
|
||||
// ensure that the body was read at least once
|
||||
data := &struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
c.Bind(data)
|
||||
|
||||
// since the unknown method is not eager cache support
|
||||
// it should fail reading the json body twice
|
||||
r := apis.RequestInfo(c)
|
||||
if v := cast.ToString(r.Data["name"]); v != "" {
|
||||
t.Fatalf("Expected empty request data body, got, %v", r.Data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
})
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
{
|
||||
Name: "api group route with supported eager cache request method",
|
||||
Method: "POST",
|
||||
Url: "/api/admins",
|
||||
Body: strings.NewReader(`{"name":"test123"}`),
|
||||
Body: strings.NewReader(rawJson),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
@@ -308,6 +294,39 @@ func TestEagerRequestInfoCache(t *testing.T) {
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
{
|
||||
Name: "custom route with @jsonPayload as multipart body",
|
||||
Method: "POST",
|
||||
Url: "/custom",
|
||||
Body: formData,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: "POST",
|
||||
Path: "/custom",
|
||||
Handler: func(c echo.Context) error {
|
||||
data := &struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
|
||||
if err := c.Bind(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// try to read the body again
|
||||
r := apis.RequestInfo(c)
|
||||
if v := cast.ToString(r.Data["name"]); v != "test123" {
|
||||
t.Fatalf("Expected request data with name %q, got, %q", "test123", v)
|
||||
}
|
||||
|
||||
return c.NoContent(200)
|
||||
},
|
||||
})
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
@@ -316,6 +335,8 @@ func TestEagerRequestInfoCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestErrorHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "apis.ApiError",
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
)
|
||||
|
||||
func TestCollectionsList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -125,6 +127,8 @@ func TestCollectionsList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionView(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -193,6 +197,8 @@ func TestCollectionView(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ensureDeletedFiles := func(app *tests.TestApp, collectionId string) {
|
||||
storageDir := filepath.Join(app.DataDir(), "storage", collectionId)
|
||||
|
||||
@@ -338,6 +344,8 @@ func TestCollectionDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -715,6 +723,8 @@ func TestCollectionCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -1082,6 +1092,8 @@ func TestCollectionUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionsImport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
totalCollections := 11
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ var defaultThumbSizes = []string{"100x100"}
|
||||
func bindFileApi(app core.App, rg *echo.Group) {
|
||||
api := fileApi{
|
||||
app: app,
|
||||
thumbGenSem: semaphore.NewWeighted(int64(runtime.NumCPU() + 1)), // the value is arbitrary chosen and may change in the future
|
||||
thumbGenSem: semaphore.NewWeighted(int64(runtime.NumCPU() + 2)), // the value is arbitrary chosen and may change in the future
|
||||
thumbGenPending: new(singleflight.Group),
|
||||
thumbGenMaxWait: 60 * time.Second,
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
)
|
||||
|
||||
func TestFileToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -92,6 +94,8 @@ func TestFileToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileDownload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, currentFile, _, _ := runtime.Caller(0)
|
||||
dataDirRelPath := "../tests/data/"
|
||||
|
||||
@@ -391,6 +395,8 @@ func TestFileDownload(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConcurrentThumbsGeneration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, err := tests.NewTestApp()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestHealthAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "health status returns 200",
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestLogsList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -78,6 +80,8 @@ func TestLogsList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLogView(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -136,6 +140,8 @@ func TestLogView(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLogsStats(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
|
||||
@@ -402,6 +402,8 @@ func realUserIp(r *http.Request, fallbackIp string) string {
|
||||
return fallbackIp
|
||||
}
|
||||
|
||||
// @todo consider removing as this may no longer be needed due to the custom rest.MultiBinder.
|
||||
//
|
||||
// eagerRequestInfoCache ensures that the request data is cached in the request
|
||||
// context to allow reading for example the json request body data more than once.
|
||||
func eagerRequestInfoCache(app core.App) echo.MiddlewareFunc {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRequireGuestOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "valid record token",
|
||||
@@ -104,6 +106,8 @@ func TestRequireGuestOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequireRecordAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "guest",
|
||||
@@ -242,6 +246,8 @@ func TestRequireRecordAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequireSameContextRecordAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "guest",
|
||||
@@ -358,6 +364,8 @@ func TestRequireSameContextRecordAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequireAdminAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "guest",
|
||||
@@ -452,6 +460,8 @@ func TestRequireAdminAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "guest (while having at least 1 existing admin)",
|
||||
@@ -571,6 +581,8 @@ func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequireAdminOrRecordAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "guest",
|
||||
@@ -731,6 +743,8 @@ func TestRequireAdminOrRecordAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequireAdminOrOwnerAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "guest",
|
||||
@@ -869,6 +883,8 @@ func TestRequireAdminOrOwnerAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadCollectionContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
|
||||
+30
-12
@@ -117,7 +117,7 @@ func (api *recordAuthApi) authMethods(c echo.Context) error {
|
||||
|
||||
provider, err := auth.NewProviderByName(name)
|
||||
if err != nil {
|
||||
api.app.Logger().Debug("Missing or invalid provier name", slog.String("name", name))
|
||||
api.app.Logger().Debug("Missing or invalid provider name", slog.String("name", name))
|
||||
continue // skip provider
|
||||
}
|
||||
|
||||
@@ -656,29 +656,42 @@ func (api *recordAuthApi) unlinkExternalAuth(c echo.Context) error {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const oauth2SubscriptionTopic = "@oauth2"
|
||||
const (
|
||||
oauth2SubscriptionTopic string = "@oauth2"
|
||||
oauth2RedirectFailurePath string = "../_/#/auth/oauth2-redirect-failure"
|
||||
oauth2RedirectSuccessPath string = "../_/#/auth/oauth2-redirect-success"
|
||||
)
|
||||
|
||||
type oauth2EventMessage struct {
|
||||
State string `json:"state"`
|
||||
Code string `json:"code"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (api *recordAuthApi) oauth2SubscriptionRedirect(c echo.Context) error {
|
||||
state := c.QueryParam("state")
|
||||
code := c.QueryParam("code")
|
||||
|
||||
if code == "" || state == "" {
|
||||
return NewBadRequestError("Invalid OAuth2 redirect parameters.", nil)
|
||||
if state == "" {
|
||||
api.app.Logger().Debug("Missing OAuth2 state parameter")
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
|
||||
client, err := api.app.SubscriptionsBroker().ClientById(state)
|
||||
if err != nil || client.IsDiscarded() || !client.HasSubscription(oauth2SubscriptionTopic) {
|
||||
return NewNotFoundError("Missing or invalid OAuth2 subscription client.", err)
|
||||
api.app.Logger().Debug("Missing or invalid OAuth2 subscription client", "error", err, "clientId", state)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
defer client.Unsubscribe(oauth2SubscriptionTopic)
|
||||
|
||||
data := map[string]string{
|
||||
"state": state,
|
||||
"code": code,
|
||||
data := oauth2EventMessage{
|
||||
State: state,
|
||||
Code: c.QueryParam("code"),
|
||||
Error: c.QueryParam("error"),
|
||||
}
|
||||
|
||||
encodedData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return NewBadRequestError("Failed to marshalize OAuth2 redirect data.", err)
|
||||
api.app.Logger().Debug("Failed to marshalize OAuth2 redirect data", "error", err)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
|
||||
msg := subscriptions.Message{
|
||||
@@ -688,5 +701,10 @@ func (api *recordAuthApi) oauth2SubscriptionRedirect(c echo.Context) error {
|
||||
|
||||
client.Send(msg)
|
||||
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "../_/#/auth/oauth2-redirect")
|
||||
if data.Error != "" || data.Code == "" {
|
||||
api.app.Logger().Debug("Failed OAuth2 redirect due to an error or missing code parameter", "error", data.Error, "clientId", data.State)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectSuccessPath)
|
||||
}
|
||||
+328
-87
@@ -17,6 +17,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordAuthMethodsList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
@@ -71,6 +73,8 @@ func TestRecordAuthMethodsList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthWithPassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "invalid body format",
|
||||
@@ -356,6 +360,8 @@ func TestRecordAuthWithPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthRefresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -483,6 +489,8 @@ func TestRecordAuthRefresh(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthRequestPasswordReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "not an auth collection",
|
||||
@@ -568,6 +576,8 @@ func TestRecordAuthRequestPasswordReset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "empty data",
|
||||
@@ -634,7 +644,7 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "valid token and data",
|
||||
Name: "valid token and data (unverified user)",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/users/confirm-password-reset",
|
||||
Body: strings.NewReader(`{
|
||||
@@ -649,6 +659,132 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if user.Verified() {
|
||||
t.Fatalf("Expected the user to be unverified")
|
||||
}
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
user, err := app.Dao().FindAuthRecordByToken(
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
app.Settings().RecordPasswordResetToken.Secret,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected the password reset token to be invalidated")
|
||||
}
|
||||
|
||||
user, err = app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if !user.Verified() {
|
||||
t.Fatalf("Expected the user to be marked as verified")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "valid token and data (unverified user with different email from the one in the token)",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/users/confirm-password-reset",
|
||||
Body: strings.NewReader(`{
|
||||
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
"password":"12345678",
|
||||
"passwordConfirm":"12345678"
|
||||
}`),
|
||||
ExpectedStatus: 204,
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnModelAfterUpdate": 1,
|
||||
"OnModelBeforeUpdate": 1,
|
||||
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if user.Verified() {
|
||||
t.Fatalf("Expected the user to be unverified")
|
||||
}
|
||||
|
||||
// manually change the email to check whether the verified state will be updated
|
||||
user.SetEmail("test_update@example.com")
|
||||
if err := app.Dao().WithoutHooks().SaveRecord(user); err != nil {
|
||||
t.Fatalf("Failed to update user test email")
|
||||
}
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
user, err := app.Dao().FindAuthRecordByToken(
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
app.Settings().RecordPasswordResetToken.Secret,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected the password reset token to be invalidated")
|
||||
}
|
||||
|
||||
user, err = app.Dao().FindAuthRecordByEmail("users", "test_update@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if user.Verified() {
|
||||
t.Fatalf("Expected the user to remain unverified")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "valid token and data (verified user)",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/users/confirm-password-reset",
|
||||
Body: strings.NewReader(`{
|
||||
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
"password":"12345678",
|
||||
"passwordConfirm":"12345678"
|
||||
}`),
|
||||
ExpectedStatus: 204,
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnModelAfterUpdate": 1,
|
||||
"OnModelBeforeUpdate": 1,
|
||||
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
// ensure that the user is already verified
|
||||
user.SetVerified(true)
|
||||
if err := app.Dao().WithoutHooks().SaveRecord(user); err != nil {
|
||||
t.Fatalf("Failed to update user verified state")
|
||||
}
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
user, err := app.Dao().FindAuthRecordByToken(
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
app.Settings().RecordPasswordResetToken.Secret,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected the password reset token to be invalidated")
|
||||
}
|
||||
|
||||
user, err = app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if !user.Verified() {
|
||||
t.Fatalf("Expected the user to remain verified")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OnRecordAfterConfirmPasswordResetRequest error response",
|
||||
@@ -681,6 +817,8 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthRequestVerification(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "not an auth collection",
|
||||
@@ -774,6 +912,8 @@ func TestRecordAuthRequestVerification(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthConfirmVerification(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "empty data",
|
||||
@@ -902,6 +1042,8 @@ func TestRecordAuthConfirmVerification(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthRequestEmailChange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -1004,6 +1146,8 @@ func TestRecordAuthRequestEmailChange(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthConfirmEmailChange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "not an auth collection",
|
||||
@@ -1124,6 +1268,8 @@ func TestRecordAuthConfirmEmailChange(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthListExternalsAuths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -1224,6 +1370,8 @@ func TestRecordAuthListExternalsAuths(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthUnlinkExternalsAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -1353,114 +1501,207 @@ func TestRecordAuthUnlinkExternalsAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthOAuth2Redirect(t *testing.T) {
|
||||
c1 := subscriptions.NewDefaultClient()
|
||||
t.Parallel()
|
||||
|
||||
c2 := subscriptions.NewDefaultClient()
|
||||
c2.Subscribe("@oauth2")
|
||||
clientStubs := make([]map[string]subscriptions.Client, 0, 10)
|
||||
|
||||
c3 := subscriptions.NewDefaultClient()
|
||||
c3.Subscribe("test1", "@oauth2")
|
||||
for i := 0; i < 10; i++ {
|
||||
c1 := subscriptions.NewDefaultClient()
|
||||
|
||||
c4 := subscriptions.NewDefaultClient()
|
||||
c4.Subscribe("test1", "test2")
|
||||
c2 := subscriptions.NewDefaultClient()
|
||||
c2.Subscribe("@oauth2")
|
||||
|
||||
c5 := subscriptions.NewDefaultClient()
|
||||
c5.Subscribe("@oauth2")
|
||||
c5.Discard()
|
||||
c3 := subscriptions.NewDefaultClient()
|
||||
c3.Subscribe("test1", "@oauth2")
|
||||
|
||||
beforeTestFunc := func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
app.SubscriptionsBroker().Register(c1)
|
||||
app.SubscriptionsBroker().Register(c2)
|
||||
app.SubscriptionsBroker().Register(c3)
|
||||
app.SubscriptionsBroker().Register(c4)
|
||||
app.SubscriptionsBroker().Register(c5)
|
||||
c4 := subscriptions.NewDefaultClient()
|
||||
c4.Subscribe("test1", "test2")
|
||||
|
||||
c5 := subscriptions.NewDefaultClient()
|
||||
c5.Subscribe("@oauth2")
|
||||
c5.Discard()
|
||||
|
||||
clientStubs = append(clientStubs, map[string]subscriptions.Client{
|
||||
"c1": c1,
|
||||
"c2": c2,
|
||||
"c3": c3,
|
||||
"c4": c4,
|
||||
"c5": c5,
|
||||
})
|
||||
}
|
||||
|
||||
checkFailureRedirect := func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
loc := res.Header.Get("Location")
|
||||
if !strings.Contains(loc, "/oauth2-redirect-failure") {
|
||||
t.Fatalf("Expected failure redirect, got %q", loc)
|
||||
}
|
||||
}
|
||||
|
||||
checkSuccessRedirect := func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
loc := res.Header.Get("Location")
|
||||
if !strings.Contains(loc, "/oauth2-redirect-success") {
|
||||
t.Fatalf("Expected success redirect, got %q", loc)
|
||||
}
|
||||
}
|
||||
|
||||
checkClientMessages := func(t *testing.T, clientId string, msg subscriptions.Message, expectedMessages map[string][]string) {
|
||||
if len(expectedMessages[clientId]) == 0 {
|
||||
t.Fatalf("Unexpected client %q message, got %s:\n%s", clientId, msg.Name, msg.Data)
|
||||
}
|
||||
|
||||
if msg.Name != "@oauth2" {
|
||||
t.Fatalf("Expected @oauth2 msg.Name, got %q", msg.Name)
|
||||
}
|
||||
|
||||
for _, txt := range expectedMessages[clientId] {
|
||||
if !strings.Contains(string(msg.Data), txt) {
|
||||
t.Fatalf("Failed to find %q in \n%s", txt, msg.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeTestFunc := func(
|
||||
clients map[string]subscriptions.Client,
|
||||
expectedMessages map[string][]string,
|
||||
) func(*testing.T, *tests.TestApp, *echo.Echo) {
|
||||
return func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
for _, client := range clients {
|
||||
app.SubscriptionsBroker().Register(client)
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
|
||||
// add to the app store so that it can be cancelled manually after test completion
|
||||
app.Store().Set("cancelFunc", cancelFunc)
|
||||
|
||||
go func() {
|
||||
defer cancelFunc()
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-clients["c1"].Channel():
|
||||
checkClientMessages(t, "c1", msg, expectedMessages)
|
||||
case msg := <-clients["c2"].Channel():
|
||||
checkClientMessages(t, "c2", msg, expectedMessages)
|
||||
case msg := <-clients["c3"].Channel():
|
||||
checkClientMessages(t, "c3", msg, expectedMessages)
|
||||
case msg := <-clients["c4"].Channel():
|
||||
checkClientMessages(t, "c4", msg, expectedMessages)
|
||||
case msg := <-clients["c5"].Channel():
|
||||
checkClientMessages(t, "c5", msg, expectedMessages)
|
||||
case <-ctx.Done():
|
||||
for _, c := range clients {
|
||||
close(c.Channel())
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "no state query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123",
|
||||
ExpectedStatus: 400,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "no state query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123",
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[0], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no code query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?state=" + c3.Id(),
|
||||
ExpectedStatus: 400,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "invalid or missing client",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=missing",
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[1], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "missing client",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=missing",
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "no code query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?state=" + clientStubs[2]["c3"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[2], map[string][]string{
|
||||
"c3": {`"state":"` + clientStubs[2]["c3"].Id(), `"code":""`},
|
||||
}),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
|
||||
if clientStubs[2]["c3"].HasSubscription("@oauth2") {
|
||||
t.Fatalf("Expected oauth2 subscription to be removed")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "discarded client with @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + c5.Id(),
|
||||
BeforeTestFunc: beforeTestFunc,
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "error query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?error=example&code=123&state=" + clientStubs[3]["c3"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[3], map[string][]string{
|
||||
"c3": {`"state":"` + clientStubs[3]["c3"].Id(), `"code":"123"`, `"error":"example"`},
|
||||
}),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
|
||||
if clientStubs[3]["c3"].HasSubscription("@oauth2") {
|
||||
t.Fatalf("Expected oauth2 subscription to be removed")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "client without @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + c4.Id(),
|
||||
BeforeTestFunc: beforeTestFunc,
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "discarded client with @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + clientStubs[4]["c5"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[4], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "client without @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + clientStubs[4]["c4"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[5], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "client with @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + c3.Id(),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
beforeTestFunc(t, app, e)
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
|
||||
go func() {
|
||||
defer cancelFunc()
|
||||
L:
|
||||
for {
|
||||
select {
|
||||
case <-c1.Channel():
|
||||
t.Error("Unexpected c1 message")
|
||||
break L
|
||||
case <-c2.Channel():
|
||||
t.Error("Unexpected c2 message")
|
||||
break L
|
||||
case msg := <-c3.Channel():
|
||||
if msg.Name != "@oauth2" {
|
||||
t.Errorf("Expected @oauth2 msg.Name, got %q", msg.Name)
|
||||
}
|
||||
|
||||
expectedParams := []string{`"state"`, `"code"`}
|
||||
for _, p := range expectedParams {
|
||||
if !strings.Contains(string(msg.Data), p) {
|
||||
t.Errorf("Couldn't find %s in \n%v", p, msg.Data)
|
||||
}
|
||||
}
|
||||
|
||||
break L
|
||||
case <-c4.Channel():
|
||||
t.Error("Unexpected c4 message")
|
||||
break L
|
||||
case <-c5.Channel():
|
||||
t.Error("Unexpected c5 message")
|
||||
break L
|
||||
case <-ctx.Done():
|
||||
t.Error("Context timeout reached")
|
||||
break L
|
||||
}
|
||||
}
|
||||
}()
|
||||
},
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + clientStubs[6]["c3"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[6], map[string][]string{
|
||||
"c3": {`"state":"` + clientStubs[6]["c3"].Id(), `"code":"123"`},
|
||||
}),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkSuccessRedirect(t, app, res)
|
||||
|
||||
if clientStubs[6]["c3"].HasSubscription("@oauth2") {
|
||||
t.Fatalf("Expected oauth2 subscription to be removed")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,13 @@ import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
func TestRecordCrudList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
@@ -493,6 +497,8 @@ func TestRecordCrudList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordCrudView(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
@@ -772,6 +778,8 @@ func TestRecordCrudView(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordCrudDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ensureDeletedFiles := func(app *tests.TestApp, collectionId string, recordId string) {
|
||||
storageDir := filepath.Join(app.DataDir(), "storage", collectionId, recordId)
|
||||
|
||||
@@ -1015,6 +1023,8 @@ func TestRecordCrudDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordCrudCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
formData, mp, err := tests.MockMultipartData(map[string]string{
|
||||
"title": "title_test",
|
||||
}, "files")
|
||||
@@ -1022,6 +1032,20 @@ func TestRecordCrudCreate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
formData2, mp2, err2 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test2", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
formData3, mp3, err3 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test3", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err3 != nil {
|
||||
t.Fatal(err3)
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
@@ -1229,6 +1253,58 @@ func TestRecordCrudCreate(t *testing.T) {
|
||||
"OnModelAfterCreate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and unsatisfied @request.data rule",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/demo3/records",
|
||||
Body: formData2,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp2.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.CreateRule = types.Pointer("@request.data.testPayload != 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection create rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 400,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and satisfied @request.data rule",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/demo3/records",
|
||||
Body: formData3,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp3.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.CreateRule = types.Pointer("@request.data.testPayload = 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection create rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`"id":"`,
|
||||
`"title":"title_test3"`,
|
||||
`"files":["`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnRecordBeforeCreateRequest": 1,
|
||||
"OnRecordAfterCreateRequest": 1,
|
||||
"OnModelBeforeCreate": 1,
|
||||
"OnModelAfterCreate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unique field error check",
|
||||
Method: http.MethodPost,
|
||||
@@ -1591,6 +1667,8 @@ func TestRecordCrudCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordCrudUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
formData, mp, err := tests.MockMultipartData(map[string]string{
|
||||
"title": "title_test",
|
||||
}, "files")
|
||||
@@ -1598,6 +1676,20 @@ func TestRecordCrudUpdate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
formData2, mp2, err2 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test2", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
formData3, mp3, err3 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test3", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err3 != nil {
|
||||
t.Fatal(err3)
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
@@ -1820,6 +1912,58 @@ func TestRecordCrudUpdate(t *testing.T) {
|
||||
"OnModelAfterUpdate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and unsatisfied @request.data rule",
|
||||
Method: http.MethodPatch,
|
||||
Url: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
||||
Body: formData2,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp2.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.UpdateRule = types.Pointer("@request.data.testPayload != 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection update rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and satisfied @request.data rule",
|
||||
Method: http.MethodPatch,
|
||||
Url: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
||||
Body: formData3,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp3.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.UpdateRule = types.Pointer("@request.data.testPayload = 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection update rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`"id":"mk5fmymtx4wsprk"`,
|
||||
`"title":"title_test3"`,
|
||||
`"files":["`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnRecordBeforeUpdateRequest": 1,
|
||||
"OnRecordAfterUpdateRequest": 1,
|
||||
"OnModelBeforeUpdate": 1,
|
||||
"OnModelAfterUpdate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OnRecordAfterUpdateRequest error response",
|
||||
Method: http.MethodPatch,
|
||||
|
||||
@@ -140,7 +140,7 @@ func RecordAuthResponse(
|
||||
// EnrichRecord parses the request context and enrich the provided record:
|
||||
// - expands relations (if defaultExpands and/or ?expand query param is set)
|
||||
// - ensures that the emails of the auth record and its expanded auth relations
|
||||
// are visibe only for the current logged admin, record owner or record with manage access
|
||||
// are visible only for the current logged admin, record owner or record with manage access
|
||||
func EnrichRecord(c echo.Context, dao *daos.Dao, record *models.Record, defaultExpands ...string) error {
|
||||
return EnrichRecords(c, dao, []*models.Record{record}, defaultExpands...)
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func EnrichRecord(c echo.Context, dao *daos.Dao, record *models.Record, defaultE
|
||||
// EnrichRecords parses the request context and enriches the provided records:
|
||||
// - expands relations (if defaultExpands and/or ?expand query param is set)
|
||||
// - ensures that the emails of the auth records and their expanded auth relations
|
||||
// are visibe only for the current logged admin, record owner or record with manage access
|
||||
// are visible only for the current logged admin, record owner or record with manage access
|
||||
func EnrichRecords(c echo.Context, dao *daos.Dao, records []*models.Record, defaultExpands ...string) error {
|
||||
requestInfo := RequestInfo(c)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRequestInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest(http.MethodPost, "/?test=123", strings.NewReader(`{"test":456}`))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
@@ -67,6 +69,8 @@ func TestRequestInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordAuthResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -83,7 +87,7 @@ func TestRecordAuthResponse(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
unverfiedAuthRecord, err := app.Dao().FindRecordById("clients", "o1y0dd0spd786md")
|
||||
unverifiedAuthRecord, err := app.Dao().FindRecordById("clients", "o1y0dd0spd786md")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -104,7 +108,7 @@ func TestRecordAuthResponse(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "valid auth record but with unverified email in onlyVerified collection",
|
||||
record: unverfiedAuthRecord,
|
||||
record: unverifiedAuthRecord,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
@@ -189,6 +193,8 @@ func TestRecordAuthResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnrichRecords(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest(http.MethodGet, "/?expand=rel_many", nil)
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSettingsList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -82,6 +84,7 @@ func TestSettingsList(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
},
|
||||
@@ -97,6 +100,8 @@ func TestSettingsList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSettingsSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validData := `{"meta":{"appName":"update_test"}}`
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
@@ -165,6 +170,7 @@ func TestSettingsSet(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
`"appName":"acme_test"`,
|
||||
@@ -237,6 +243,7 @@ func TestSettingsSet(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
`"appName":"update_test"`,
|
||||
@@ -278,6 +285,8 @@ func TestSettingsSet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSettingsTestS3(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -344,6 +353,8 @@ func TestSettingsTestS3(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSettingsTestEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@@ -508,6 +519,8 @@ func TestSettingsTestEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateAppleClientSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
+16
-6
@@ -28,12 +28,10 @@ func NewAdminCommand(app core.App) *cobra.Command {
|
||||
|
||||
func adminCreateCommand(app core.App) *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "create",
|
||||
Example: "admin create test@example.com 1234567890",
|
||||
Short: "Creates a new admin account",
|
||||
// prevents printing the error log twice
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
Use: "create",
|
||||
Example: "admin create test@example.com 1234567890",
|
||||
Short: "Creates a new admin account",
|
||||
SilenceUsage: true,
|
||||
RunE: func(command *cobra.Command, args []string) error {
|
||||
if len(args) != 2 {
|
||||
return errors.New("Missing email and password arguments.")
|
||||
@@ -51,6 +49,10 @@ func adminCreateCommand(app core.App) *cobra.Command {
|
||||
admin.Email = args[0]
|
||||
admin.SetPassword(args[1])
|
||||
|
||||
if !app.Dao().HasTable(admin.TableName()) {
|
||||
return errors.New("Migration are not initialized yet. Please run 'migrate up' and try again.")
|
||||
}
|
||||
|
||||
if err := app.Dao().SaveAdmin(admin); err != nil {
|
||||
return fmt.Errorf("Failed to create new admin account: %v", err)
|
||||
}
|
||||
@@ -84,6 +86,10 @@ func adminUpdateCommand(app core.App) *cobra.Command {
|
||||
return errors.New("The new password must be at least 8 chars long.")
|
||||
}
|
||||
|
||||
if !app.Dao().HasTable((&models.Admin{}).TableName()) {
|
||||
return errors.New("Migration are not initialized yet. Please run 'migrate up' and try again.")
|
||||
}
|
||||
|
||||
admin, err := app.Dao().FindAdminByEmail(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Admin with email %s doesn't exist.", args[0])
|
||||
@@ -116,6 +122,10 @@ func adminDeleteCommand(app core.App) *cobra.Command {
|
||||
return errors.New("Invalid or missing email address.")
|
||||
}
|
||||
|
||||
if !app.Dao().HasTable((&models.Admin{}).TableName()) {
|
||||
return errors.New("Migration are not initialized yet. Please run 'migrate up' and try again.")
|
||||
}
|
||||
|
||||
admin, err := app.Dao().FindAdminByEmail(args[0])
|
||||
if err != nil {
|
||||
color.Yellow("Admin %s is already deleted.", args[0])
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminCreateCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
+10
-7
@@ -1,7 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
@@ -17,10 +17,11 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
|
||||
var httpsAddr string
|
||||
|
||||
command := &cobra.Command{
|
||||
Use: "serve [domain(s)]",
|
||||
Args: cobra.ArbitraryArgs,
|
||||
Short: "Starts the web server (default to 127.0.0.1:8090 if no domain is specified)",
|
||||
Run: func(command *cobra.Command, args []string) {
|
||||
Use: "serve [domain(s)]",
|
||||
Args: cobra.ArbitraryArgs,
|
||||
Short: "Starts the web server (default to 127.0.0.1:8090 if no domain is specified)",
|
||||
SilenceUsage: true,
|
||||
RunE: func(command *cobra.Command, args []string) error {
|
||||
// set default listener addresses if at least one domain is specified
|
||||
if len(args) > 0 {
|
||||
if httpAddr == "" {
|
||||
@@ -43,9 +44,11 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
|
||||
CertificateDomains: args,
|
||||
})
|
||||
|
||||
if err != http.ErrServerClosed {
|
||||
log.Fatalln(err)
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -20,6 +22,8 @@ func TestAdminQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindAdminById(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -47,6 +51,8 @@ func TestFindAdminById(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindAdminByEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -76,6 +82,8 @@ func TestFindAdminByEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindAdminByToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -131,6 +139,8 @@ func TestFindAdminByToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTotalAdmins(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -155,6 +165,8 @@ func TestTotalAdmins(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsAdminEmailUnique(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -180,6 +192,8 @@ func TestIsAdminEmailUnique(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteAdmin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -225,6 +239,8 @@ func TestDeleteAdmin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveAdmin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
+9
-9
@@ -18,7 +18,7 @@ func New(db dbx.Builder) *Dao {
|
||||
return NewMultiDB(db, db)
|
||||
}
|
||||
|
||||
// New creates a new Dao instance with the provided dedicated
|
||||
// NewMultiDB creates a new Dao instance with the provided dedicated
|
||||
// async and sync db builders.
|
||||
func NewMultiDB(concurrentDB, nonconcurrentDB dbx.Builder) *Dao {
|
||||
return &Dao{
|
||||
@@ -87,16 +87,16 @@ func (dao *Dao) Clone() *Dao {
|
||||
// WithoutHooks returns a new Dao with the same configuration options
|
||||
// as the current one, but without create/update/delete hooks.
|
||||
func (dao *Dao) WithoutHooks() *Dao {
|
||||
new := dao.Clone()
|
||||
clone := dao.Clone()
|
||||
|
||||
new.BeforeCreateFunc = nil
|
||||
new.AfterCreateFunc = nil
|
||||
new.BeforeUpdateFunc = nil
|
||||
new.AfterUpdateFunc = nil
|
||||
new.BeforeDeleteFunc = nil
|
||||
new.AfterDeleteFunc = nil
|
||||
clone.BeforeCreateFunc = nil
|
||||
clone.AfterCreateFunc = nil
|
||||
clone.BeforeUpdateFunc = nil
|
||||
clone.AfterUpdateFunc = nil
|
||||
clone.BeforeDeleteFunc = nil
|
||||
clone.AfterDeleteFunc = nil
|
||||
|
||||
return new
|
||||
return clone
|
||||
}
|
||||
|
||||
// ModelQuery creates a new preconfigured select query with preset
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
)
|
||||
|
||||
func TestGetDefaultRetryInterval(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if i := getDefaultRetryInterval(-1); i.Milliseconds() != 1000 {
|
||||
t.Fatalf("Expected 1000ms, got %v", i)
|
||||
}
|
||||
@@ -20,6 +22,8 @@ func TestGetDefaultRetryInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBaseLockRetry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
err error
|
||||
failUntilAttempt int
|
||||
|
||||
+5
-5
@@ -162,8 +162,8 @@ func (dao *Dao) DeleteCollection(collection *models.Collection) error {
|
||||
// SaveCollection persists the provided Collection model and updates
|
||||
// its related records table schema.
|
||||
//
|
||||
// If collecction.IsNew() is true, the method will perform a create, otherwise an update.
|
||||
// To explicitly mark a collection for update you can use collecction.MarkAsNotNew().
|
||||
// If collection.IsNew() is true, the method will perform a create, otherwise an update.
|
||||
// To explicitly mark a collection for update you can use collection.MarkAsNotNew().
|
||||
func (dao *Dao) SaveCollection(collection *models.Collection) error {
|
||||
var oldCollection *models.Collection
|
||||
|
||||
@@ -263,11 +263,11 @@ func (dao *Dao) ImportCollections(
|
||||
|
||||
// extend existing schema
|
||||
if !deleteMissing {
|
||||
schema, _ := existing.Schema.Clone()
|
||||
schemaClone, _ := existing.Schema.Clone()
|
||||
for _, f := range imported.Schema.Fields() {
|
||||
schema.AddField(f) // add or replace
|
||||
schemaClone.AddField(f) // add or replace
|
||||
}
|
||||
imported.Schema = *schema
|
||||
imported.Schema = *schemaClone
|
||||
}
|
||||
} else {
|
||||
imported.MarkAsNew()
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
)
|
||||
|
||||
func TestCollectionQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -28,6 +30,8 @@ func TestCollectionQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindCollectionsByType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -63,6 +67,8 @@ func TestFindCollectionsByType(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindCollectionByNameOrId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -92,6 +98,8 @@ func TestFindCollectionByNameOrId(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsCollectionNameUnique(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -116,6 +124,8 @@ func TestIsCollectionNameUnique(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindCollectionReferences(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -164,6 +174,8 @@ func TestFindCollectionReferences(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteCollection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -251,6 +263,8 @@ func TestDeleteCollection(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveCollectionCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -297,6 +311,8 @@ func TestSaveCollectionCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveCollectionUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -336,6 +352,8 @@ func TestSaveCollectionUpdate(t *testing.T) {
|
||||
|
||||
// indirect update of a field used in view should cause view(s) update
|
||||
func TestSaveCollectionIndirectViewsUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -395,6 +413,8 @@ func TestSaveCollectionIndirectViewsUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveCollectionViewWrapping(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
viewName := "test_wrapping"
|
||||
|
||||
scenarios := []struct {
|
||||
@@ -504,6 +524,8 @@ func TestSaveCollectionViewWrapping(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestImportCollections(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
totalCollections := 11
|
||||
|
||||
scenarios := []struct {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestExternalAuthQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -21,6 +23,8 @@ func TestExternalAuthQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindAllExternalAuthsByRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -58,6 +62,8 @@ func TestFindAllExternalAuthsByRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindFirstExternalAuthByExpr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -89,6 +95,8 @@ func TestFindFirstExternalAuthByExpr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindExternalAuthByRecordAndProvider(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -125,6 +133,8 @@ func TestFindExternalAuthByRecordAndProvider(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveExternalAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -161,6 +171,8 @@ func TestSaveExternalAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteExternalAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestLogQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -24,6 +26,8 @@ func TestLogQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindLogById(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -54,6 +58,8 @@ func TestFindLogById(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLogsStats(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -75,6 +81,8 @@ func TestLogsStats(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteOldLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -115,6 +123,8 @@ func TestDeleteOldLogs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func TestParamQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -23,6 +25,8 @@ func TestParamQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindParamByKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -50,6 +54,8 @@ func TestFindParamByKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -92,6 +98,8 @@ func TestSaveParam(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveParamEncrypted(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -126,6 +134,8 @@ func TestSaveParamEncrypted(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
+24
-9
@@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
@@ -658,26 +659,40 @@ func (dao *Dao) DeleteRecord(record *models.Record) error {
|
||||
//
|
||||
// NB! This method is expected to be called inside a transaction.
|
||||
func (dao *Dao) cascadeRecordDelete(mainRecord *models.Record, refs map[*models.Collection][]*schema.SchemaField) error {
|
||||
uniqueJsonEachAlias := "__je__" + security.PseudorandomString(4)
|
||||
// @todo consider changing refs to a slice
|
||||
//
|
||||
// Sort the refs keys to ensure that the cascade events firing order is always the same.
|
||||
// This is not necessary for the operation to function correctly but it helps having deterministic output during testing.
|
||||
sortedRefKeys := make([]*models.Collection, 0, len(refs))
|
||||
for k := range refs {
|
||||
sortedRefKeys = append(sortedRefKeys, k)
|
||||
}
|
||||
sort.Slice(sortedRefKeys, func(i, j int) bool {
|
||||
return sortedRefKeys[i].Name < sortedRefKeys[j].Name
|
||||
})
|
||||
|
||||
for refCollection, fields := range refs {
|
||||
if refCollection.IsView() {
|
||||
continue // skip view collections
|
||||
for _, refCollection := range sortedRefKeys {
|
||||
fields, ok := refs[refCollection]
|
||||
|
||||
if refCollection.IsView() || !ok {
|
||||
continue // skip missing or view collections
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
recordTableName := inflector.Columnify(refCollection.Name)
|
||||
prefixedFieldName := recordTableName + "." + inflector.Columnify(field.Name)
|
||||
|
||||
query := dao.RecordQuery(refCollection).Distinct(true)
|
||||
query := dao.RecordQuery(refCollection)
|
||||
|
||||
if opt, ok := field.Options.(schema.MultiValuer); !ok || !opt.IsMultiple() {
|
||||
query.AndWhere(dbx.HashExp{prefixedFieldName: mainRecord.Id})
|
||||
} else {
|
||||
query.InnerJoin(fmt.Sprintf(
|
||||
`json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END) as {{%s}}`,
|
||||
prefixedFieldName, prefixedFieldName, prefixedFieldName, uniqueJsonEachAlias,
|
||||
), dbx.HashExp{uniqueJsonEachAlias + ".value": mainRecord.Id})
|
||||
query.AndWhere(dbx.Exists(dbx.NewExp(fmt.Sprintf(
|
||||
`SELECT 1 FROM json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END) {{__je__}} WHERE [[__je__.value]]={:jevalue}`,
|
||||
prefixedFieldName, prefixedFieldName, prefixedFieldName,
|
||||
), dbx.Params{
|
||||
"jevalue": mainRecord.Id,
|
||||
})))
|
||||
}
|
||||
|
||||
if refCollection.Id == mainRecord.Collection().Id {
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
)
|
||||
|
||||
func TestExpandRecords(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -222,6 +224,8 @@ func TestExpandRecords(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExpandRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -372,6 +376,8 @@ func TestExpandRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndirectExpandSingeVsArrayResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSyncRecordTableSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -132,6 +134,8 @@ func TestSyncRecordTableSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSingleVsMultipleValuesNormalization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
+53
-2
@@ -19,6 +19,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordQueryWithDifferentCollectionValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -58,6 +60,8 @@ func TestRecordQueryWithDifferentCollectionValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordQueryOneWithRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -82,6 +86,8 @@ func TestRecordQueryOneWithRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordQueryAllWithRecordsSlices(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -143,6 +149,8 @@ func TestRecordQueryAllWithRecordsSlices(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindRecordById(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -203,6 +211,8 @@ func TestFindRecordById(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindRecordsByIds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -294,6 +304,8 @@ func TestFindRecordsByIds(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindRecordsByExpr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -364,6 +376,8 @@ func TestFindRecordsByExpr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindFirstRecordByData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -427,6 +441,8 @@ func TestFindFirstRecordByData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindRecordsByFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -530,6 +546,19 @@ func TestFindRecordsByFilter(t *testing.T) {
|
||||
"llvuca81nly1qls",
|
||||
},
|
||||
},
|
||||
{
|
||||
"with json filter and sort",
|
||||
"demo4",
|
||||
"json_object != null && json_object.a.b = 'test'",
|
||||
"-json_object.a",
|
||||
10,
|
||||
0,
|
||||
[]dbx.Params{{"active": false}},
|
||||
false,
|
||||
[]string{
|
||||
"i9naidtvr6qsgb4",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
@@ -566,6 +595,8 @@ func TestFindRecordsByFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindFirstRecordByFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -647,6 +678,8 @@ func TestFindFirstRecordByFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCanAccessRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -814,6 +847,8 @@ func TestCanAccessRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsRecordValueUnique(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -863,6 +898,8 @@ func TestIsRecordValueUnique(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindAuthRecordByToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -925,6 +962,8 @@ func TestFindAuthRecordByToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindAuthRecordByEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -956,6 +995,8 @@ func TestFindAuthRecordByEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindAuthRecordByUsername(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -988,6 +1029,8 @@ func TestFindAuthRecordByUsername(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSuggestUniqueAuthRecordUsername(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -1025,6 +1068,8 @@ func TestSuggestUniqueAuthRecordUsername(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -1058,6 +1103,8 @@ func TestSaveRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveRecordWithIdFromOtherCollection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -1090,6 +1137,8 @@ func TestSaveRecordWithIdFromOtherCollection(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -1156,17 +1205,19 @@ func TestDeleteRecord(t *testing.T) {
|
||||
}
|
||||
// ensure that the json rel fields were prefixed
|
||||
joinedQueries := strings.Join(calledQueries, " ")
|
||||
expectedRelManyPart := "`demo1` INNER JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END)"
|
||||
expectedRelManyPart := "SELECT `demo1`.* FROM `demo1` WHERE EXISTS (SELECT 1 FROM json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) {{__je__}} WHERE [[__je__.value]]='"
|
||||
if !strings.Contains(joinedQueries, expectedRelManyPart) {
|
||||
t.Fatalf("(rec3) Expected the cascade delete to call the query \n%v, got \n%v", expectedRelManyPart, calledQueries)
|
||||
}
|
||||
expectedRelOnePart := "SELECT DISTINCT `demo1`.* FROM `demo1` WHERE (`demo1`.`rel_one`="
|
||||
expectedRelOnePart := "SELECT `demo1`.* FROM `demo1` WHERE (`demo1`.`rel_one`='"
|
||||
if !strings.Contains(joinedQueries, expectedRelOnePart) {
|
||||
t.Fatalf("(rec3) Expected the cascade delete to call the query \n%v, got \n%v", expectedRelOnePart, calledQueries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRecordBatchProcessing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSaveAndFindSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestHasTable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -36,6 +38,8 @@ func TestHasTable(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTableColumns(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -64,6 +68,8 @@ func TestTableColumns(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTableInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -92,6 +98,8 @@ func TestTableInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteTable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -116,6 +124,8 @@ func TestDeleteTable(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVacuum(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -141,6 +151,8 @@ func TestVacuum(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTableIndexes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ func ensureNoTempViews(app core.App, t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteView(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -60,6 +62,8 @@ func TestDeleteView(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveView(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -180,6 +184,8 @@ func TestSaveView(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateViewSchemaWithDiscardedNestedTransaction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -196,6 +202,8 @@ func TestCreateViewSchemaWithDiscardedNestedTransaction(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateViewSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -486,6 +494,8 @@ func TestCreateViewSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindRecordByViewFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminLoginValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -51,6 +53,8 @@ func TestAdminLoginValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminLoginInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminPasswordResetConfirmValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -99,6 +101,8 @@ func TestAdminPasswordResetConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminPasswordResetConfirmInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminPasswordResetRequestValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -74,6 +76,8 @@ func TestAdminPasswordResetRequestValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminPasswordResetRequestInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNewAdminUpsert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -31,6 +33,8 @@ func TestNewAdminUpsert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminUpsertValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -183,6 +187,8 @@ func TestAdminUpsertValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminUpsertSubmitInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -231,6 +237,8 @@ func TestAdminUpsertSubmitInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminUpsertWithCustomId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAppleClientSecretCreateValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestBackupCreateValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
backupName string
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestBackupUploadValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var zb bytes.Buffer
|
||||
zw := zip.NewWriter(&zb)
|
||||
if err := zw.Close(); err != nil {
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNewCollectionUpsert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -88,6 +90,8 @@ func TestNewCollectionUpsert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -654,6 +658,8 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionUpsertSubmitInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -704,6 +710,8 @@ func TestCollectionUpsertSubmitInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionUpsertWithCustomId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func TestCollectionsImportValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -38,6 +40,8 @@ func TestCollectionsImportValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionsImportSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
totalCollections := 11
|
||||
|
||||
scenarios := []struct {
|
||||
@@ -461,6 +465,8 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionsImportSubmitInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRealtimeSubscribeValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
clientId string
|
||||
expectError bool
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordEmailChangeConfirmValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -145,6 +147,8 @@ func TestRecordEmailChangeConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordEmailChangeConfirmInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordEmailChangeRequestValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -106,6 +108,8 @@ func TestRecordEmailChangeRequestValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordEmailChangeRequestInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestUserOauth2LoginValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordPasswordLoginValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -132,6 +134,8 @@ func TestRecordPasswordLoginValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordPasswordLoginInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/forms/validators"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// RecordPasswordResetConfirm is an auth record password reset confirmation form.
|
||||
@@ -91,9 +93,21 @@ func (form *RecordPasswordResetConfirm) Submit(interceptors ...InterceptorFunc[*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !authRecord.Verified() {
|
||||
payload, err := security.ParseUnverifiedJWT(form.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// mark as verified if the email hasn't changed
|
||||
if authRecord.Email() == cast.ToString(payload["email"]) {
|
||||
authRecord.SetVerified(true)
|
||||
}
|
||||
}
|
||||
|
||||
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {
|
||||
authRecord = m
|
||||
return form.dao.SaveRecord(m)
|
||||
return form.dao.SaveRecord(authRecord)
|
||||
}, interceptors...)
|
||||
|
||||
if interceptorsErr != nil {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordPasswordResetConfirmValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -136,6 +138,8 @@ func TestRecordPasswordResetConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordPasswordResetConfirmInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ func (form *RecordPasswordResetRequest) SetDao(dao *daos.Dao) {
|
||||
|
||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
|
||||
//
|
||||
// This method doesn't checks whether auth record with `form.Email` exists (this is done on Submit).
|
||||
// This method doesn't check whether auth record with `form.Email` exists (this is done on Submit).
|
||||
func (form *RecordPasswordResetRequest) Validate() error {
|
||||
return validation.ValidateStruct(form,
|
||||
validation.Field(
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordPasswordResetRequestSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -116,6 +118,8 @@ func TestRecordPasswordResetRequestSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordPasswordResetRequestInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
+14
-4
@@ -165,7 +165,7 @@ func (form *RecordUpsert) extractMultipartFormData(
|
||||
|
||||
data := map[string]any{}
|
||||
filesToUpload := map[string][]*filesystem.File{}
|
||||
arrayValueSupportTypes := schema.ArraybleFieldTypes()
|
||||
arraybleFieldTypes := schema.ArraybleFieldTypes()
|
||||
|
||||
for fullKey, values := range r.PostForm {
|
||||
key := fullKey
|
||||
@@ -178,8 +178,18 @@ func (form *RecordUpsert) extractMultipartFormData(
|
||||
continue
|
||||
}
|
||||
|
||||
// special case for multipart json encoded fields
|
||||
if key == rest.MultipartJsonKey {
|
||||
for _, v := range values {
|
||||
if err := json.Unmarshal([]byte(v), &data); err != nil {
|
||||
form.app.Logger().Debug("Failed to decode @json value into the data map", "error", err, "value", v)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
field := form.record.Collection().Schema.GetFieldByName(key)
|
||||
if field != nil && list.ExistInSlice(field.Type, arrayValueSupportTypes) {
|
||||
if field != nil && list.ExistInSlice(field.Type, arraybleFieldTypes) {
|
||||
data[key] = values
|
||||
} else {
|
||||
data[key] = values[0]
|
||||
@@ -313,10 +323,10 @@ func (form *RecordUpsert) AddFiles(key string, files ...*filesystem.File) error
|
||||
// Example
|
||||
//
|
||||
// // mark only only 2 files for removal
|
||||
// form.AddFiles("documents", "file1_aw4bdrvws6.txt", "file2_xwbs36bafv.txt")
|
||||
// form.RemoveFiles("documents", "file1_aw4bdrvws6.txt", "file2_xwbs36bafv.txt")
|
||||
//
|
||||
// // mark all "documents" files for removal
|
||||
// form.AddFiles("documents")
|
||||
// form.RemoveFiles("documents")
|
||||
func (form *RecordUpsert) RemoveFiles(key string, toDelete ...string) error {
|
||||
field := form.record.Collection().Schema.GetFieldByName(key)
|
||||
if field == nil || field.Type != schema.FieldTypeFile {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
@@ -150,9 +151,10 @@ func TestRecordUpsertLoadRequestMultipart(t *testing.T) {
|
||||
}
|
||||
|
||||
formData, mp, err := tests.MockMultipartData(map[string]string{
|
||||
"a.b.id": "test_id",
|
||||
"a.b.text": "test123",
|
||||
"a.b.unknown": "test456",
|
||||
"a.b.id": "test_id",
|
||||
"a.b.text": "test123",
|
||||
"a.b.unknown": "test456",
|
||||
"a.b." + rest.MultipartJsonKey: `{"json":["a","b"],"email":"test3@example.com"}`,
|
||||
// file fields unset/delete
|
||||
"a.b.file_one-": "test_d61b33QdDU.txt", // delete with modifier
|
||||
"a.b.file_many.0": "", // delete by index
|
||||
@@ -184,6 +186,19 @@ func TestRecordUpsertLoadRequestMultipart(t *testing.T) {
|
||||
t.Fatalf("Didn't expect unknown field to be set, got %v", v)
|
||||
}
|
||||
|
||||
if v, ok := form.Data()["email"]; !ok || v != "test3@example.com" {
|
||||
t.Fatalf("Expect email field to be %q, got %q", "test3@example.com", v)
|
||||
}
|
||||
|
||||
rawJsonValue, ok := form.Data()["json"].(types.JsonRaw)
|
||||
if !ok {
|
||||
t.Fatal("Expect json field to be set")
|
||||
}
|
||||
expectedJsonValue := `["a","b"]`
|
||||
if rawJsonValue.String() != expectedJsonValue {
|
||||
t.Fatalf("Expect json field %v, got %v", expectedJsonValue, rawJsonValue)
|
||||
}
|
||||
|
||||
fileOne, ok := form.Data()["file_one"]
|
||||
if !ok {
|
||||
t.Fatal("Expect file_one field to be set")
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordVerificationConfirmValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -98,6 +100,8 @@ func TestRecordVerificationConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordVerificationConfirmInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRecordVerificationRequestSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -134,6 +136,8 @@ func TestRecordVerificationRequestSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordVerificationRequestInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNewSettingsUpsert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -30,6 +32,8 @@ func TestNewSettingsUpsert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSettingsUpsertValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -127,6 +131,8 @@ func TestSettingsUpsertValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSettingsUpsertSubmitInterceptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestEmailSendValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
template string
|
||||
email string
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestS3FilesystemValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -66,6 +68,8 @@ func TestS3FilesystemValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestS3FilesystemSubmitFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestUploadedFileSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data, mp, err := tests.MockMultipartData(nil, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -52,6 +54,8 @@ func TestUploadedFileSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUploadedFileMimeType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data, mp, err := tests.MockMultipartData(nil, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
)
|
||||
|
||||
// Compare checks whether the provided model id exists.
|
||||
// UniqueId checks whether the provided model id already exists.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestUniqueId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ func TestRecordDataValidatorEmptyAndUnknown(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordDataValidatorValidateText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
valA string
|
||||
valB string
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/aws/aws-sdk-go v1.48.16
|
||||
github.com/aws/aws-sdk-go v1.50.2
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2
|
||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||
@@ -17,39 +17,39 @@ require (
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/pocketbase/dbx v1.10.1
|
||||
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4
|
||||
github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342
|
||||
github.com/spf13/cast v1.6.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
gocloud.dev v0.35.0
|
||||
golang.org/x/crypto v0.16.0
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/oauth2 v0.15.0
|
||||
golang.org/x/sync v0.5.0
|
||||
modernc.org/sqlite v1.27.0
|
||||
gocloud.dev v0.36.0
|
||||
golang.org/x/crypto v0.18.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/oauth2 v0.16.0
|
||||
golang.org/x/sync v0.6.0
|
||||
modernc.org/sqlite v1.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
@@ -57,7 +57,7 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
@@ -71,23 +71,23 @@ require (
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/image v0.14.0 // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.16.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/api v0.153.0 // indirect
|
||||
google.golang.org/api v0.157.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/grpc v1.61.0 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
modernc.org/cc/v3 v3.41.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.15 // indirect
|
||||
modernc.org/libc v1.37.0 // indirect
|
||||
modernc.org/libc v1.40.7 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
|
||||
@@ -17,44 +17,44 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.48.16 h1:mcj2/9J/MJ55Dov+ocMevhR8Jv6jW/fAxbrn4a1JFc8=
|
||||
github.com/aws/aws-sdk-go v1.48.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go v1.50.2 h1:/vS+Uhv2FPcqcTxBmgT3tvvN5q6pMAKu6QXltgXlGgo=
|
||||
github.com/aws/aws-sdk-go v1.50.2/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 h1:FnLf60PtjXp8ZOzQfhJVsqF0OtYKQZWQfqOLshh8YXg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7/go.mod h1:tDVvl8hyU6E9B8TrnNrZQEVkQlB8hjJwcgpPhgtlnNg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.14 h1:ogP1WgyvN/qxPJkgtFMD7G2eKb5p/61Jomx+nIHXUQ4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.14/go.mod h1:nYd/WmIrXlBHW/5QwrZP81/Gz08wKi87nV6EI1kmqx4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10/go.mod h1:FHbKWQtRBYUz4vO5WBWjzMD2by126ny5y/1EoaWoLfI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 h1:L0ai8WICYHozIKK+OtPzVJBugL7culcuM4E4JOpIEm8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10/go.mod h1:byqfyxJBshFk0fF9YmK0M0ugIO8OWjzH2T3bPG4eGuA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -93,6 +93,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
@@ -101,6 +103,10 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ganigeorgiev/fexpr v0.4.0 h1:ojitI+VMNZX/odeNL1x3RzTTE8qAIVvnSSYPNAnQFDI=
|
||||
github.com/ganigeorgiev/fexpr v0.4.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
@@ -153,8 +159,8 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
||||
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
@@ -190,8 +196,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
@@ -199,8 +205,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA=
|
||||
github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4 h1:85kAYIKrKEeau7WgXg8B7Km8etrVavJAyH7XcR5MkFw=
|
||||
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
|
||||
github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342 h1:OcAwewen3hs/zY8i0syt8CcMTGBJhQwQRVDLcoQVXVk=
|
||||
github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
@@ -231,17 +237,27 @@ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
gocloud.dev v0.35.0 h1:x/Gtt5OJdT4j+ir1AXAIXb7bBnFawXAAaJptCUGk3HU=
|
||||
gocloud.dev v0.35.0/go.mod h1:wbyF+BhfdtLWyUtVEWRW13hFLb1vXnV2ovEhYGQe3ck=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus=
|
||||
gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -258,17 +274,17 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -281,12 +297,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -306,14 +322,14 @@ golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
|
||||
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4=
|
||||
google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
|
||||
google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20=
|
||||
google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
@@ -322,19 +338,19 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg=
|
||||
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
|
||||
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg=
|
||||
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -346,8 +362,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -371,16 +387,16 @@ modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.37.0 h1:WerjebcsP6A7Jy+f2lCnHAkiSTLf7IaSftBYUtoswak=
|
||||
modernc.org/libc v1.37.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
||||
modernc.org/libc v1.40.7 h1:oeLS0G067ZqUu+v143Dqad0btMfKmNS7SuOsnkq0Ysg=
|
||||
modernc.org/libc v1.40.7/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
|
||||
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
||||
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
||||
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSendAdminPasswordReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ func SendRecordVerification(app core.App, authRecord *models.Record) error {
|
||||
})
|
||||
}
|
||||
|
||||
// SendUserChangeEmail sends a change email confirmation email to the specified user.
|
||||
// SendRecordChangeEmail sends a change email confirmation email to the specified user.
|
||||
func SendRecordChangeEmail(app core.App, record *models.Record, newEmail string) error {
|
||||
token, tokenErr := tokens.NewRecordChangeEmailToken(app, record, newEmail)
|
||||
if tokenErr != nil {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSendRecordPasswordReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -37,6 +39,8 @@ func TestSendRecordPasswordReset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSendRecordVerification(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -62,6 +66,8 @@ func TestSendRecordVerification(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSendRecordChangeEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ func init() {
|
||||
}
|
||||
|
||||
options, _ := f.Options.(*schema.JsonOptions)
|
||||
if options != nil {
|
||||
if options == nil {
|
||||
options = &schema.JsonOptions{}
|
||||
}
|
||||
options.MaxSize = 2000000 // 2mb
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminTableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.Admin{}
|
||||
if m.TableName() != "_admins" {
|
||||
t.Fatalf("Unexpected table name, got %q", m.TableName())
|
||||
@@ -15,6 +17,8 @@ func TestAdminTableName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminValidatePassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
admin models.Admin
|
||||
password string
|
||||
@@ -61,6 +65,8 @@ func TestAdminValidatePassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminSetPassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.Admin{
|
||||
// 123456
|
||||
PasswordHash: "$2a$10$SKk/Y/Yc925PBtsSYBvq3Ous9Jy18m4KTn6b/PQQ.Y9QVjy3o/Fv.",
|
||||
@@ -93,6 +99,8 @@ func TestAdminSetPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminRefreshTokenKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.Admin{TokenKey: "test"}
|
||||
|
||||
m.RefreshTokenKey()
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestBaseModelHasId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
model models.BaseModel
|
||||
expected bool
|
||||
@@ -34,6 +36,8 @@ func TestBaseModelHasId(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBaseModelId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.BaseModel{}
|
||||
|
||||
if m.GetId() != "" {
|
||||
@@ -54,6 +58,8 @@ func TestBaseModelId(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBaseModelIsNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m0 := models.BaseModel{}
|
||||
m1 := models.BaseModel{Id: ""}
|
||||
m2 := models.BaseModel{Id: "test"}
|
||||
@@ -96,6 +102,8 @@ func TestBaseModelIsNew(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBaseModelCreated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.BaseModel{}
|
||||
|
||||
if !m.GetCreated().IsZero() {
|
||||
@@ -110,6 +118,8 @@ func TestBaseModelCreated(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBaseModelUpdated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.BaseModel{}
|
||||
|
||||
if !m.GetUpdated().IsZero() {
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func TestCollectionTableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.Collection{}
|
||||
if m.TableName() != "_collections" {
|
||||
t.Fatalf("Unexpected table name, got %q", m.TableName())
|
||||
@@ -18,6 +20,8 @@ func TestCollectionTableName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionBaseFilesPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.Collection{}
|
||||
|
||||
m.RefreshId()
|
||||
@@ -29,6 +33,8 @@ func TestCollectionBaseFilesPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionIsBase(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collection models.Collection
|
||||
expected bool
|
||||
@@ -48,6 +54,8 @@ func TestCollectionIsBase(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionIsAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collection models.Collection
|
||||
expected bool
|
||||
@@ -67,6 +75,8 @@ func TestCollectionIsAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionMarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
collection models.Collection
|
||||
@@ -109,6 +119,8 @@ func TestCollectionMarshalJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionBaseOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
collection models.Collection
|
||||
@@ -153,6 +165,8 @@ func TestCollectionBaseOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionAuthOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
options := types.JsonMap{"test": 123, "minPasswordLength": 4}
|
||||
expectedSerialization := `{"manageRule":null,"allowOAuth2Auth":false,"allowUsernameAuth":false,"allowEmailAuth":false,"requireEmail":false,"exceptEmailDomains":null,"onlyVerified":false,"onlyEmailDomains":null,"minPasswordLength":4}`
|
||||
|
||||
@@ -200,6 +214,8 @@ func TestCollectionAuthOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionViewOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
options := types.JsonMap{"query": "select id from demo1", "minPasswordLength": 4}
|
||||
expectedSerialization := `{"query":"select id from demo1"}`
|
||||
|
||||
@@ -247,6 +263,8 @@ func TestCollectionViewOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNormalizeOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
collection models.Collection
|
||||
@@ -288,6 +306,8 @@ func TestNormalizeOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.Collection{
|
||||
Options: types.JsonMap{"test": 123},
|
||||
}
|
||||
@@ -306,6 +326,8 @@ func TestDecodeOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
collection models.Collection
|
||||
@@ -357,6 +379,8 @@ func TestSetOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionBaseOptionsValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opt := models.CollectionBaseOptions{}
|
||||
if err := opt.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -364,6 +388,8 @@ func TestCollectionBaseOptionsValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionAuthOptionsValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
options models.CollectionAuthOptions
|
||||
@@ -451,6 +477,8 @@ func TestCollectionAuthOptionsValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionViewOptionsValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
options models.CollectionViewOptions
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestExternalAuthTableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.ExternalAuth{}
|
||||
if m.TableName() != "_externalAuths" {
|
||||
t.Fatalf("Unexpected table name, got %q", m.TableName())
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestParamTableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.Param{}
|
||||
if m.TableName() != "_params" {
|
||||
t.Fatalf("Unexpected table name, got %q", m.TableName())
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNewRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Name: "test_collection",
|
||||
Schema: schema.NewSchema(
|
||||
@@ -37,6 +39,8 @@ func TestNewRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewRecordFromNullStringMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Name: "test",
|
||||
Schema: schema.NewSchema(
|
||||
@@ -200,6 +204,8 @@ func TestNewRecordFromNullStringMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewRecordsFromNullStringMaps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Name: "test",
|
||||
Schema: schema.NewSchema(
|
||||
@@ -310,6 +316,8 @@ func TestNewRecordsFromNullStringMaps(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordTableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
collection.Name = "test"
|
||||
collection.RefreshId()
|
||||
@@ -322,6 +330,8 @@ func TestRecordTableName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordCollection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
collection.RefreshId()
|
||||
|
||||
@@ -333,6 +343,8 @@ func TestRecordCollection(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordOriginalCopy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.NewRecord(&models.Collection{})
|
||||
m.Load(map[string]any{"f": "123"})
|
||||
|
||||
@@ -360,6 +372,8 @@ func TestRecordOriginalCopy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordCleanCopy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.NewRecord(&models.Collection{
|
||||
Name: "cname",
|
||||
Type: models.CollectionTypeAuth,
|
||||
@@ -392,6 +406,8 @@ func TestRecordCleanCopy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordSetAndGetExpand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
m := models.NewRecord(collection)
|
||||
|
||||
@@ -409,6 +425,8 @@ func TestRecordSetAndGetExpand(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordMergeExpand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
m := models.NewRecord(collection)
|
||||
m.Id = "m"
|
||||
@@ -501,6 +519,8 @@ func TestRecordMergeExpand(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordMergeExpandNilCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
|
||||
scenarios := []struct {
|
||||
@@ -542,6 +562,8 @@ func TestRecordMergeExpandNilCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordExpandedRel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
|
||||
main := models.NewRecord(collection)
|
||||
@@ -574,6 +596,8 @@ func TestRecordExpandedRel(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordExpandedAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
|
||||
main := models.NewRecord(collection)
|
||||
@@ -606,6 +630,8 @@ func TestRecordExpandedAll(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordSchemaData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Type: models.CollectionTypeAuth,
|
||||
Schema: schema.NewSchema(
|
||||
@@ -639,6 +665,8 @@ func TestRecordSchemaData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordUnknownData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
@@ -719,6 +747,8 @@ func TestRecordUnknownData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordSetAndGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
@@ -799,6 +829,8 @@ func TestRecordSetAndGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordGetBool(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expected bool
|
||||
@@ -830,6 +862,8 @@ func TestRecordGetBool(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordGetString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expected string
|
||||
@@ -860,6 +894,8 @@ func TestRecordGetString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordGetInt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expected int
|
||||
@@ -892,6 +928,8 @@ func TestRecordGetInt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordGetFloat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expected float64
|
||||
@@ -924,6 +962,8 @@ func TestRecordGetFloat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordGetTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nowTime := time.Now()
|
||||
testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")
|
||||
|
||||
@@ -957,6 +997,8 @@ func TestRecordGetTime(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordGetDateTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nowTime := time.Now()
|
||||
testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")
|
||||
|
||||
@@ -990,6 +1032,8 @@ func TestRecordGetDateTime(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordGetStringSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nowTime := time.Now()
|
||||
|
||||
scenarios := []struct {
|
||||
@@ -1031,6 +1075,8 @@ func TestRecordGetStringSlice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordUnmarshalJSONField(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(&schema.SchemaField{
|
||||
Name: "field",
|
||||
@@ -1086,6 +1132,8 @@ func TestRecordUnmarshalJSONField(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordBaseFilesPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{}
|
||||
collection.RefreshId()
|
||||
collection.Name = "test"
|
||||
@@ -1102,6 +1150,8 @@ func TestRecordBaseFilesPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordFindFileFieldByFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
@@ -1159,6 +1209,8 @@ func TestRecordFindFileFieldByFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordLoadAndData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
@@ -1232,6 +1284,8 @@ func TestRecordLoadAndData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordColumnValueMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
@@ -1319,6 +1373,8 @@ func TestRecordColumnValueMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordPublicExportAndMarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Name: "c_name",
|
||||
Schema: schema.NewSchema(
|
||||
@@ -1463,6 +1519,8 @@ func TestRecordPublicExportAndMarshalJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordUnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
@@ -1551,6 +1609,8 @@ func TestRecordUnmarshalJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordReplaceModifers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := &models.Collection{
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
@@ -1658,6 +1718,8 @@ func TestRecordReplaceModifers(t *testing.T) {
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func TestRecordUsername(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
expectError bool
|
||||
@@ -1699,6 +1761,8 @@ func TestRecordUsername(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
expectError bool
|
||||
@@ -1740,6 +1804,8 @@ func TestRecordEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordEmailVisibility(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
value bool
|
||||
@@ -1782,6 +1848,8 @@ func TestRecordEmailVisibility(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordEmailVerified(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
value bool
|
||||
@@ -1824,6 +1892,8 @@ func TestRecordEmailVerified(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordTokenKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
expectError bool
|
||||
@@ -1865,6 +1935,8 @@ func TestRecordTokenKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordRefreshTokenKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
expectError bool
|
||||
@@ -1904,6 +1976,8 @@ func TestRecordRefreshTokenKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordLastResetSentAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
expectError bool
|
||||
@@ -1948,6 +2022,8 @@ func TestRecordLastResetSentAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordLastVerificationSentAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
expectError bool
|
||||
@@ -1992,6 +2068,8 @@ func TestRecordLastVerificationSentAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordPasswordHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := models.NewRecord(&models.Collection{})
|
||||
|
||||
if v := m.PasswordHash(); v != "" {
|
||||
@@ -2006,6 +2084,8 @@ func TestRecordPasswordHash(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordValidatePassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// 123456
|
||||
hash := "$2a$10$YKU8mPP8sTE3xZrpuM.xQuq27KJ7aIJB2oUeKPsDDqZshbl5g5cDK"
|
||||
|
||||
@@ -2034,6 +2114,8 @@ func TestRecordValidatePassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordSetPassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionType string
|
||||
password string
|
||||
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
RequestAuthRecord = "authRecord"
|
||||
)
|
||||
|
||||
// Deprecated: Replaced by the Log model and will be removed in a future version.
|
||||
type Request struct {
|
||||
BaseModel
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRequestInfoHasModifierDataKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
requestInfo *models.RequestInfo
|
||||
|
||||
@@ -65,6 +65,7 @@ type Settings struct {
|
||||
YandexAuth AuthProviderConfig `form:"yandexAuth" json:"yandexAuth"`
|
||||
PatreonAuth AuthProviderConfig `form:"patreonAuth" json:"patreonAuth"`
|
||||
MailcowAuth AuthProviderConfig `form:"mailcowAuth" json:"mailcowAuth"`
|
||||
BitbucketAuth AuthProviderConfig `form:"bitbucketAuth" json:"bitbucketAuth"`
|
||||
}
|
||||
|
||||
// New creates and returns a new default Settings instance.
|
||||
@@ -196,6 +197,9 @@ func New() *Settings {
|
||||
MailcowAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
BitbucketAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +245,7 @@ func (s *Settings) Validate() error {
|
||||
validation.Field(&s.YandexAuth),
|
||||
validation.Field(&s.PatreonAuth),
|
||||
validation.Field(&s.MailcowAuth),
|
||||
validation.Field(&s.BitbucketAuth),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -309,6 +314,7 @@ func (s *Settings) RedactClone() (*Settings, error) {
|
||||
&clone.YandexAuth.ClientSecret,
|
||||
&clone.PatreonAuth.ClientSecret,
|
||||
&clone.MailcowAuth.ClientSecret,
|
||||
&clone.BitbucketAuth.ClientSecret,
|
||||
}
|
||||
|
||||
// mask all sensitive fields
|
||||
@@ -351,6 +357,7 @@ func (s *Settings) NamedAuthProviderConfigs() map[string]AuthProviderConfig {
|
||||
auth.NameYandex: s.YandexAuth,
|
||||
auth.NamePatreon: s.PatreonAuth,
|
||||
auth.NameMailcow: s.MailcowAuth,
|
||||
auth.NameBitbucket: s.BitbucketAuth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +523,7 @@ type EmailTemplate struct {
|
||||
Body string `form:"body" json:"body"`
|
||||
Subject string `form:"subject" json:"subject"`
|
||||
ActionUrl string `form:"actionUrl" json:"actionUrl"`
|
||||
Hidden bool `form:"hidden" json:"hidden"`
|
||||
}
|
||||
|
||||
// Validate makes EmailTemplate validatable by implementing [validation.Validatable] interface.
|
||||
|
||||
@@ -78,6 +78,8 @@ func TestSettingsValidate(t *testing.T) {
|
||||
s.PatreonAuth.ClientId = ""
|
||||
s.MailcowAuth.Enabled = true
|
||||
s.MailcowAuth.ClientId = ""
|
||||
s.BitbucketAuth.Enabled = true
|
||||
s.BitbucketAuth.ClientId = ""
|
||||
|
||||
// check if Validate() is triggering the members validate methods.
|
||||
err := s.Validate()
|
||||
@@ -121,6 +123,7 @@ func TestSettingsValidate(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
}
|
||||
|
||||
errBytes, _ := json.Marshal(err)
|
||||
@@ -198,6 +201,8 @@ func TestSettingsMerge(t *testing.T) {
|
||||
s2.PatreonAuth.ClientId = "patreon_test"
|
||||
s2.MailcowAuth.Enabled = true
|
||||
s2.MailcowAuth.ClientId = "mailcow_test"
|
||||
s2.BitbucketAuth.Enabled = true
|
||||
s2.BitbucketAuth.ClientId = "bitbucket_test"
|
||||
|
||||
if err := s1.Merge(s2); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -290,6 +295,7 @@ func TestSettingsRedactClone(t *testing.T) {
|
||||
s1.YandexAuth.ClientSecret = testSecret
|
||||
s1.PatreonAuth.ClientSecret = testSecret
|
||||
s1.MailcowAuth.ClientSecret = testSecret
|
||||
s1.BitbucketAuth.ClientSecret = testSecret
|
||||
|
||||
s1Bytes, err := json.Marshal(s1)
|
||||
if err != nil {
|
||||
@@ -350,6 +356,7 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
||||
s.YandexAuth.ClientId = "yandex_test"
|
||||
s.PatreonAuth.ClientId = "patreon_test"
|
||||
s.MailcowAuth.ClientId = "mailcow_test"
|
||||
s.BitbucketAuth.ClientId = "bitbucket_test"
|
||||
|
||||
result := s.NamedAuthProviderConfigs()
|
||||
|
||||
@@ -383,6 +390,7 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
||||
`"yandex":{"enabled":false,"clientId":"yandex_test"`,
|
||||
`"patreon":{"enabled":false,"clientId":"patreon_test"`,
|
||||
`"mailcow":{"enabled":false,"clientId":"mailcow_test"`,
|
||||
`"bitbucket":{"enabled":false,"clientId":"bitbucket_test"`,
|
||||
}
|
||||
for _, p := range expectedParts {
|
||||
if !strings.Contains(encodedStr, p) {
|
||||
|
||||
@@ -106,7 +106,7 @@ func (p *plugin) updateCmd() *cobra.Command {
|
||||
|
||||
command := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Automatically updates the current PocketBase executable with the latest available version",
|
||||
Short: "Automatically updates the current app executable with the latest available version",
|
||||
SilenceUsage: true,
|
||||
RunE: func(command *cobra.Command, args []string) error {
|
||||
var needConfirm bool
|
||||
@@ -160,7 +160,7 @@ func (p *plugin) update(withBackup bool) error {
|
||||
}
|
||||
|
||||
if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 {
|
||||
color.Green("You already have the latest PocketBase %s.", p.currentVersion)
|
||||
color.Green("You already have the latest version %s.", p.currentVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/dop251/goja"
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/labstack/echo/v5/middleware"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
@@ -618,6 +619,8 @@ func apisBinds(vm *goja.Runtime) {
|
||||
obj.Set("requireAdminOrRecordAuth", apis.RequireAdminOrRecordAuth)
|
||||
obj.Set("requireAdminOrOwnerAuth", apis.RequireAdminOrOwnerAuth)
|
||||
obj.Set("activityLogger", apis.ActivityLogger)
|
||||
obj.Set("gzip", middleware.Gzip)
|
||||
obj.Set("bodyLimit", middleware.BodyLimit)
|
||||
|
||||
// record helpers
|
||||
obj.Set("requestInfo", apis.RequestInfo)
|
||||
|
||||
@@ -956,7 +956,7 @@ func TestApisBindsCount(t *testing.T) {
|
||||
apisBinds(vm)
|
||||
|
||||
testBindsCount(vm, "this", 6, t)
|
||||
testBindsCount(vm, "$apis", 12, t)
|
||||
testBindsCount(vm, "$apis", 14, t)
|
||||
}
|
||||
|
||||
func TestApisBindsApiError(t *testing.T) {
|
||||
|
||||
+3466
-3435
File diff suppressed because it is too large.
Load diff
@@ -218,7 +218,7 @@ declare function readerToString(reader: any, maxBytes?: number): string;
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* slee(250) // sleeps for 250ms
|
||||
* sleep(250) // sleeps for 250ms
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
@@ -978,6 +978,8 @@ declare namespace $apis {
|
||||
let activityLogger: apis.activityLogger
|
||||
let requestInfo: apis.requestInfo
|
||||
let recordAuthResponse: apis.recordAuthResponse
|
||||
let gzip: middleware.gzip
|
||||
let bodyLimit: middleware.bodyLimit
|
||||
let enrichRecord: apis.enrichRecord
|
||||
let enrichRecords: apis.enrichRecords
|
||||
}
|
||||
@@ -1053,6 +1055,7 @@ func main() {
|
||||
|
||||
gen := tygoja.New(tygoja.Config{
|
||||
Packages: map[string][]string{
|
||||
"github.com/labstack/echo/v5/middleware": {"Gzip", "BodyLimit"},
|
||||
"github.com/go-ozzo/ozzo-validation/v4": {"Error"},
|
||||
"github.com/pocketbase/dbx": {"*"},
|
||||
"github.com/pocketbase/pocketbase/tools/security": {"*"},
|
||||
|
||||
@@ -453,6 +453,17 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
result.MultiMatchSubQuery = r.multiMatch
|
||||
}
|
||||
|
||||
// wrap in json_extract to ensure that top-level primitives
|
||||
// stored as json work correctly when compared to their SQL equivalent
|
||||
// (https://github.com/pocketbase/pocketbase/issues/4068)
|
||||
if field.Type == schema.FieldTypeJson {
|
||||
result.NoCoalesce = true
|
||||
result.Identifier = jsonExtract(r.activeTableAlias+"."+cleanFieldName, "")
|
||||
if r.withMultiMatch {
|
||||
r.multiMatch.valueIdentifier = jsonExtract(r.multiMatchActiveTableAlias+"."+cleanFieldName, "")
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -467,7 +478,6 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
// check if it is a json field
|
||||
if field.Type == schema.FieldTypeJson {
|
||||
var jsonPath strings.Builder
|
||||
jsonPath.WriteString("$")
|
||||
for _, p := range r.activeProps[i+1:] {
|
||||
if _, err := strconv.Atoi(p); err == nil {
|
||||
jsonPath.WriteString("[")
|
||||
@@ -478,23 +488,15 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
jsonPath.WriteString(inflector.Columnify(p))
|
||||
}
|
||||
}
|
||||
jsonPathStr := jsonPath.String()
|
||||
|
||||
result := &search.ResolverResult{
|
||||
Identifier: fmt.Sprintf(
|
||||
"JSON_EXTRACT([[%s.%s]], '%s')",
|
||||
r.activeTableAlias,
|
||||
inflector.Columnify(prop),
|
||||
jsonPath.String(),
|
||||
),
|
||||
NoCoalesce: true,
|
||||
Identifier: jsonExtract(r.activeTableAlias+"."+inflector.Columnify(prop), jsonPathStr),
|
||||
}
|
||||
|
||||
if r.withMultiMatch {
|
||||
r.multiMatch.valueIdentifier = fmt.Sprintf(
|
||||
"JSON_EXTRACT([[%s.%s]], '%s')",
|
||||
r.multiMatchActiveTableAlias,
|
||||
inflector.Columnify(prop),
|
||||
jsonPath.String(),
|
||||
)
|
||||
r.multiMatch.valueIdentifier = jsonExtract(r.multiMatchActiveTableAlias+"."+inflector.Columnify(prop), jsonPathStr)
|
||||
result.MultiMatchSubQuery = r.multiMatch
|
||||
}
|
||||
|
||||
@@ -601,6 +603,18 @@ func jsonEach(tableColumnPair string) string {
|
||||
)
|
||||
}
|
||||
|
||||
func jsonExtract(tableColumnPair string, path string) string {
|
||||
return fmt.Sprintf(
|
||||
// note: the extra object wrapping is needed to workaround the cases where a json_extract is used with non-json columns.
|
||||
"(CASE WHEN json_valid([[%s]]) THEN JSON_EXTRACT([[%s]], '$%s') ELSE JSON_EXTRACT(json_object('pb', [[%s]]), '$.pb%s') END)",
|
||||
tableColumnPair,
|
||||
tableColumnPair,
|
||||
path,
|
||||
tableColumnPair,
|
||||
path,
|
||||
)
|
||||
}
|
||||
|
||||
func resolvableSystemFieldNames(collection *models.Collection) []string {
|
||||
result := schema.BaseModelFieldNames()
|
||||
|
||||
|
||||
@@ -106,9 +106,10 @@ func NewRecordFieldResolver(
|
||||
r.staticRequestInfo["data"] = r.requestInfo.Data
|
||||
r.staticRequestInfo["auth"] = nil
|
||||
if r.requestInfo.AuthRecord != nil {
|
||||
r.requestInfo.AuthRecord.IgnoreEmailVisibility(true)
|
||||
r.staticRequestInfo["auth"] = r.requestInfo.AuthRecord.PublicExport()
|
||||
r.requestInfo.AuthRecord.IgnoreEmailVisibility(false)
|
||||
authData := r.requestInfo.AuthRecord.PublicExport()
|
||||
// always add the record email no matter of the emailVisibility field
|
||||
authData[schema.FieldNameEmail] = r.requestInfo.AuthRecord.Email()
|
||||
r.staticRequestInfo["auth"] = authData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,14 +143,14 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
|
||||
"demo4",
|
||||
"self_rel_many.title ?= 'test' || self_rel_one.json_object.a ?> true",
|
||||
false,
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ([[demo4_self_rel_many.title]] = {:TEST} OR JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') > 1)",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ([[demo4_self_rel_many.title]] = {:TEST} OR (CASE WHEN json_valid([[demo4_self_rel_one.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_one.json_object]]), '$.pb.a') END) > 1)",
|
||||
},
|
||||
{
|
||||
"multiple rels (multi-match operators)",
|
||||
"demo4",
|
||||
"self_rel_many.title = 'test' || self_rel_one.json_object.a > true",
|
||||
false,
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ((([[demo4_self_rel_many.title]] = {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many.title]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] = {:TEST})))) OR JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') > 1)",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ((([[demo4_self_rel_many.title]] = {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many.title]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] = {:TEST})))) OR (CASE WHEN json_valid([[demo4_self_rel_one.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_one.json_object]]), '$.pb.a') END) > 1)",
|
||||
},
|
||||
{
|
||||
"@collection join (opt/any operators)",
|
||||
@@ -301,7 +301,21 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
|
||||
"demo4",
|
||||
"json_object.a.b = '' && self_rel_many:length != 2 && json_object.a.b > 3 && self_rel_many:length <= 4",
|
||||
false,
|
||||
"SELECT `demo4`.* FROM `demo4` WHERE ((JSON_EXTRACT([[demo4.json_object]], '$.a.b') = '' OR JSON_EXTRACT([[demo4.json_object]], '$.a.b') IS NULL) AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) IS NOT {:TEST} AND JSON_EXTRACT([[demo4.json_object]], '$.a.b') > {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) <= {:TEST})",
|
||||
"SELECT `demo4`.* FROM `demo4` WHERE ((CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$.a.b') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb.a.b') END) IS {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) IS NOT {:TEST} AND (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$.a.b') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb.a.b') END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) <= {:TEST})",
|
||||
},
|
||||
{
|
||||
"json field equal normalization checks",
|
||||
"demo4",
|
||||
"json_object = '' || json_object != '' || '' = json_object || '' != json_object ||" +
|
||||
"json_object = null || json_object != null || null = json_object || null != json_object ||" +
|
||||
"json_object = true || json_object != true || true = json_object || true != json_object ||" +
|
||||
"json_object = json_object || json_object != json_object ||" +
|
||||
"json_object = title || title != json_object ||" +
|
||||
// multimatch expressions
|
||||
"self_rel_many.json_object = '' || null = self_rel_many.json_object ||" +
|
||||
"self_rel_many.json_object = self_rel_many.json_object",
|
||||
false,
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] WHERE ((CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS {:TEST} OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT {:TEST} OR {:TEST} IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR {:TEST} IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NULL OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT NULL OR NULL IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR NULL IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS 1 OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT 1 OR 1 IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR 1 IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS [[demo4.title]] OR [[demo4.title]] IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (((CASE WHEN json_valid([[demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_many.json_object]]), '$.pb') END) IS {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT (CASE WHEN json_valid([[__mm_demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[__mm_demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[__mm_demo4_self_rel_many.json_object]]), '$.pb') END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] IS {:TEST})))) OR ((NULL IS (CASE WHEN json_valid([[demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_many.json_object]]), '$.pb') END)) AND (NOT EXISTS (SELECT 1 FROM (SELECT (CASE WHEN json_valid([[__mm_demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[__mm_demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[__mm_demo4_self_rel_many.json_object]]), '$.pb') END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN jsonLine truncated
|
||||
},
|
||||
}
|
||||
|
||||
@@ -381,8 +395,8 @@ func TestRecordFieldResolverResolveSchemaFields(t *testing.T) {
|
||||
{"self_rel_many.title", false, "[[demo4_self_rel_many.title]]"},
|
||||
{"self_rel_many.self_rel_one.self_rel_many.title", false, "[[demo4_self_rel_many_self_rel_one_self_rel_many.title]]"},
|
||||
// json_extract
|
||||
{"json_array.0", false, "JSON_EXTRACT([[demo4.json_array]], '$[0]')"},
|
||||
{"json_object.a.b.c", false, "JSON_EXTRACT([[demo4.json_object]], '$.a.b.c')"},
|
||||
{"json_array.0", false, "(CASE WHEN json_valid([[demo4.json_array]]) THEN JSON_EXTRACT([[demo4.json_array]], '$[0]') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_array]]), '$.pb[0]') END)"},
|
||||
{"json_object.a.b.c", false, "(CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$.a.b.c') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb.a.b.c') END)"},
|
||||
// @request.auth relation join:
|
||||
{"@request.auth.rel", false, "[[__auth_users.rel]]"},
|
||||
{"@request.auth.rel.title", false, "[[__auth_users_rel.title]]"},
|
||||
@@ -405,26 +419,27 @@ func TestRecordFieldResolverResolveSchemaFields(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
r, err := r.Resolve(s.fieldName)
|
||||
t.Run(s.fieldName, func(t *testing.T) {
|
||||
r, err := r.Resolve(s.fieldName)
|
||||
|
||||
hasErr := err != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Errorf("(%q) Expected hasErr %v, got %v (%v)", s.fieldName, s.expectError, hasErr, err)
|
||||
continue
|
||||
}
|
||||
hasErr := err != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
|
||||
}
|
||||
|
||||
if hasErr {
|
||||
continue
|
||||
}
|
||||
if hasErr {
|
||||
return
|
||||
}
|
||||
|
||||
if r.Identifier != s.expectName {
|
||||
t.Errorf("(%q) Expected r.Identifier %q, got %q", s.fieldName, s.expectName, r.Identifier)
|
||||
}
|
||||
if r.Identifier != s.expectName {
|
||||
t.Fatalf("Expected r.Identifier %q, got %q", s.expectName, r.Identifier)
|
||||
}
|
||||
|
||||
// params should be empty for non @request fields
|
||||
if len(r.Params) != 0 {
|
||||
t.Errorf("(%q) Expected 0 r.Params, got %v", s.fieldName, r.Params)
|
||||
}
|
||||
// params should be empty for non @request fields
|
||||
if len(r.Params) != 0 {
|
||||
t.Fatalf("Expected 0 r.Params, got %v", r.Params)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,56 +501,64 @@ func TestRecordFieldResolverResolveStaticRequestInfoFields(t *testing.T) {
|
||||
{"@request.data.c", false, `"{\"sub\":1}"`},
|
||||
{"@request.auth", true, ""},
|
||||
{"@request.auth.id", false, `"4q1xlclmfloku33"`},
|
||||
{"@request.auth.email", false, `"test@example.com"`},
|
||||
{"@request.auth.username", false, `"users75657"`},
|
||||
{"@request.auth.verified", false, `false`},
|
||||
{"@request.auth.emailVisibility", false, `false`},
|
||||
{"@request.auth.email", false, `"test@example.com"`}, // should always be returned no matter of the emailVisibility state
|
||||
{"@request.auth.missing", false, `NULL`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
r, err := r.Resolve(s.fieldName)
|
||||
t.Run(s.fieldName, func(t *testing.T) {
|
||||
r, err := r.Resolve(s.fieldName)
|
||||
|
||||
hasErr := err != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Errorf("(%d) Expected hasErr %v, got %v (%v)", i, s.expectError, hasErr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if hasErr {
|
||||
continue
|
||||
}
|
||||
|
||||
// missing key
|
||||
// ---
|
||||
if len(r.Params) == 0 {
|
||||
if r.Identifier != "NULL" {
|
||||
t.Errorf("(%d) Expected 0 placeholder parameters for %v, got %v", i, r.Identifier, r.Params)
|
||||
hasErr := err != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Fatalf("(%d) Expected hasErr %v, got %v (%v)", i, s.expectError, hasErr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// existing key
|
||||
// ---
|
||||
if len(r.Params) != 1 {
|
||||
t.Errorf("(%d) Expected 1 placeholder parameter for %v, got %v", i, r.Identifier, r.Params)
|
||||
continue
|
||||
}
|
||||
if hasErr {
|
||||
return
|
||||
}
|
||||
|
||||
var paramName string
|
||||
var paramValue any
|
||||
for k, v := range r.Params {
|
||||
paramName = k
|
||||
paramValue = v
|
||||
}
|
||||
// missing key
|
||||
// ---
|
||||
if len(r.Params) == 0 {
|
||||
if r.Identifier != "NULL" {
|
||||
t.Fatalf("(%d) Expected 0 placeholder parameters for %v, got %v", i, r.Identifier, r.Params)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if r.Identifier != ("{:" + paramName + "}") {
|
||||
t.Errorf("(%d) Expected parameter r.Identifier %q, got %q", i, paramName, r.Identifier)
|
||||
}
|
||||
// existing key
|
||||
// ---
|
||||
if len(r.Params) != 1 {
|
||||
t.Fatalf("(%d) Expected 1 placeholder parameter for %v, got %v", i, r.Identifier, r.Params)
|
||||
}
|
||||
|
||||
encodedParamValue, _ := json.Marshal(paramValue)
|
||||
if string(encodedParamValue) != s.expectParamValue {
|
||||
t.Errorf("(%d) Expected r.Params %v for %v, got %v", i, s.expectParamValue, r.Identifier, string(encodedParamValue))
|
||||
}
|
||||
var paramName string
|
||||
var paramValue any
|
||||
for k, v := range r.Params {
|
||||
paramName = k
|
||||
paramValue = v
|
||||
}
|
||||
|
||||
if r.Identifier != ("{:" + paramName + "}") {
|
||||
t.Fatalf("(%d) Expected parameter r.Identifier %q, got %q", i, paramName, r.Identifier)
|
||||
}
|
||||
|
||||
encodedParamValue, _ := json.Marshal(paramValue)
|
||||
if string(encodedParamValue) != s.expectParamValue {
|
||||
t.Fatalf("(%d) Expected r.Params %v for %v, got %v", i, s.expectParamValue, r.Identifier, string(encodedParamValue))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ensure that the original email visibility was restored
|
||||
if authRecord.EmailVisibility() {
|
||||
t.Fatal("Expected the original authRecord emailVisibility to remain unchanged")
|
||||
}
|
||||
if v, ok := authRecord.PublicExport()["email"]; ok {
|
||||
t.Fatalf("Expected the original authRecord email to not be exported, got %q", v)
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -38,18 +38,21 @@ type TestApp struct {
|
||||
func (t *TestApp) Cleanup() {
|
||||
t.ResetEventCalls()
|
||||
t.ResetBootstrapState()
|
||||
t.TestMailer.Reset()
|
||||
|
||||
if t.DataDir() != "" {
|
||||
os.RemoveAll(t.DataDir())
|
||||
}
|
||||
}
|
||||
|
||||
// NewMailClient initializes test app mail client.
|
||||
// NewMailClient initializes (if not already) a test app mail client.
|
||||
func (t *TestApp) NewMailClient() mailer.Mailer {
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
t.TestMailer.Reset()
|
||||
if t.TestMailer == nil {
|
||||
t.TestMailer = &TestMailer{}
|
||||
}
|
||||
|
||||
return t.TestMailer
|
||||
}
|
||||
|
||||
Binary file not shown.
+11
-6
@@ -10,18 +10,23 @@ var _ mailer.Mailer = (*TestMailer)(nil)
|
||||
type TestMailer struct {
|
||||
TotalSend int
|
||||
LastMessage mailer.Message
|
||||
|
||||
// @todo consider deprecating the above 2 fields?
|
||||
SentMessages []mailer.Message
|
||||
}
|
||||
|
||||
// Reset clears any previously test collected data.
|
||||
func (m *TestMailer) Reset() {
|
||||
m.TotalSend = 0
|
||||
m.LastMessage = mailer.Message{}
|
||||
func (tm *TestMailer) Reset() {
|
||||
tm.TotalSend = 0
|
||||
tm.LastMessage = mailer.Message{}
|
||||
tm.SentMessages = nil
|
||||
}
|
||||
|
||||
// Send implements `mailer.Mailer` interface.
|
||||
func (c *TestMailer) Send(m *mailer.Message) error {
|
||||
c.TotalSend++
|
||||
c.LastMessage = *m
|
||||
func (tm *TestMailer) Send(m *mailer.Message) error {
|
||||
tm.TotalSend++
|
||||
tm.LastMessage = *m
|
||||
tm.SentMessages = append(tm.SentMessages, tm.LastMessage)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNewAdminAuthToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -31,6 +33,8 @@ func TestNewAdminAuthToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewAdminResetPasswordToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
@@ -54,6 +58,8 @@ func TestNewAdminResetPasswordToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewAdminFileToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
Loaded 100 of 193 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user