build: add test-all make targets
This commit is contained in:
parent
319d7a383f
commit
ea4abe350b
26
Makefile
26
Makefile
@ -16,7 +16,7 @@ APP_BUILD_NUMBER := $(if $(APP_BUILD_NUMBER_RAW),$(APP_BUILD_NUMBER_RAW),1)
|
||||
APP_DART_DEFINE_VERSION ?= --dart-define=XWORKMATE_DISPLAY_VERSION=$(APP_VERSION)
|
||||
APP_DART_DEFINE_BUILD ?= --dart-define=XWORKMATE_BUILD_NUMBER=$(APP_BUILD_NUMBER)
|
||||
|
||||
.PHONY: help deps analyze test check format run open-macos-xcode sync-version build-linux build-macos build-ios-sim package-deb package-rpm package-linux package-mac install-mac clean build-go-core render-release-docs check-export-compliance
|
||||
.PHONY: help deps analyze test test-all test-flutter test-golden test-integration test-integration-macos test-patrol test-go test-ci check format run open-macos-xcode sync-version build-linux build-macos build-ios-sim package-deb package-rpm package-linux package-mac install-mac clean build-go-core render-release-docs check-export-compliance
|
||||
|
||||
help: ## Show available targets
|
||||
@grep -E '^[a-zA-Z0-9_.-]+:.*?## ' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-18s %s\n", $$1, $$2}'
|
||||
@ -30,6 +30,30 @@ analyze: ## Run static analysis
|
||||
test: ## Run Flutter tests
|
||||
$(FLUTTER) test
|
||||
|
||||
test-flutter: ## Run the full Flutter unit/widget test suite
|
||||
$(FLUTTER) test
|
||||
|
||||
test-golden: ## Run Flutter Golden tests
|
||||
$(FLUTTER) test test/golden
|
||||
|
||||
test-integration: ## Run Flutter integration tests
|
||||
$(FLUTTER) test integration_test
|
||||
|
||||
test-integration-macos: ## Run macOS integration tests serially for the desktop app
|
||||
$(FLUTTER) test integration_test/desktop_navigation_flow_test.dart -d macos
|
||||
$(FLUTTER) test integration_test/desktop_settings_flow_test.dart -d macos
|
||||
|
||||
test-patrol: ## Run Patrol end-to-end tests
|
||||
dart pub global activate patrol_cli
|
||||
patrol test
|
||||
|
||||
test-go: ## Run Go API unit tests
|
||||
cd go_service && go test ./...
|
||||
|
||||
test-ci: test-flutter test-golden test-integration test-go ## Run the PR validation chain
|
||||
|
||||
test-all: test-ci test-patrol ## Run the full local validation chain
|
||||
|
||||
check: analyze test ## Run the standard validation suite
|
||||
|
||||
format: ## Format Dart sources
|
||||
|
||||
49
go/go_core/internal/handler/auth_handler.go
Normal file
49
go/go_core/internal/handler/auth_handler.go
Normal file
@ -0,0 +1,49 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"xworkmate/go_core/internal/service"
|
||||
)
|
||||
|
||||
type Authenticator interface {
|
||||
Authenticate(username, password string) error
|
||||
}
|
||||
|
||||
type AuthHandler struct {
|
||||
service Authenticator
|
||||
}
|
||||
|
||||
func NewAuthHandler(svc Authenticator) *AuthHandler {
|
||||
return &AuthHandler{service: svc}
|
||||
}
|
||||
|
||||
func NewServiceAdapter(svc *service.AuthService) Authenticator {
|
||||
return authServiceAdapter{service: svc}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var payload struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
http.Error(w, "invalid json", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := h.service.Authenticate(payload.Username, payload.Password); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
type authServiceAdapter struct {
|
||||
service *service.AuthService
|
||||
}
|
||||
|
||||
func (a authServiceAdapter) Authenticate(username, password string) error {
|
||||
return a.service.Authenticate(nil, username, password)
|
||||
}
|
||||
53
go/go_core/internal/handler/auth_handler_test.go
Normal file
53
go/go_core/internal/handler/auth_handler_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fakeAuthenticator struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (f fakeAuthenticator) Authenticate(username, password string) error {
|
||||
return f.err
|
||||
}
|
||||
|
||||
func TestAuthHandlerRejectsInvalidJSON(t *testing.T) {
|
||||
handler := NewAuthHandler(fakeAuthenticator{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/auth", bytes.NewBufferString("{"))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandlerReturnsUnauthorizedOnServiceFailure(t *testing.T) {
|
||||
handler := NewAuthHandler(fakeAuthenticator{err: errors.New("invalid credentials")})
|
||||
req := httptest.NewRequest(http.MethodPost, "/auth", bytes.NewBufferString(`{"username":"alice","password":"secret"}`))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("expected 401, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandlerReturnsOKOnSuccess(t *testing.T) {
|
||||
handler := NewAuthHandler(fakeAuthenticator{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/auth", bytes.NewBufferString(`{"username":"alice","password":"secret"}`))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
37
go/go_core/internal/service/auth_service.go
Normal file
37
go/go_core/internal/service/auth_service.go
Normal file
@ -0,0 +1,37 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
|
||||
type AuthRepository interface {
|
||||
Verify(ctx context.Context, username, password string) (bool, error)
|
||||
}
|
||||
|
||||
type AuthService struct {
|
||||
repo AuthRepository
|
||||
}
|
||||
|
||||
func NewAuthService(repo AuthRepository) *AuthService {
|
||||
return &AuthService{repo: repo}
|
||||
}
|
||||
|
||||
func (s *AuthService) Authenticate(ctx context.Context, username, password string) error {
|
||||
username = strings.TrimSpace(username)
|
||||
password = strings.TrimSpace(password)
|
||||
if username == "" || password == "" {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
ok, err := s.repo.Verify(ctx, username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
return nil
|
||||
}
|
||||
55
go/go_core/internal/service/auth_service_test.go
Normal file
55
go/go_core/internal/service/auth_service_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fakeAuthRepo struct {
|
||||
verify func(ctx context.Context, username, password string) (bool, error)
|
||||
}
|
||||
|
||||
func (f fakeAuthRepo) Verify(ctx context.Context, username, password string) (bool, error) {
|
||||
return f.verify(ctx, username, password)
|
||||
}
|
||||
|
||||
func TestAuthenticateRejectsBlankValues(t *testing.T) {
|
||||
svc := NewAuthService(fakeAuthRepo{
|
||||
verify: func(ctx context.Context, username, password string) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
})
|
||||
|
||||
if err := svc.Authenticate(context.Background(), " ", "secret"); !errors.Is(err, ErrInvalidCredentials) {
|
||||
t.Fatalf("expected invalid credentials, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticateRejectsFailedVerification(t *testing.T) {
|
||||
svc := NewAuthService(fakeAuthRepo{
|
||||
verify: func(ctx context.Context, username, password string) (bool, error) {
|
||||
if username != "alice" || password != "secret" {
|
||||
t.Fatalf("unexpected credentials: %q %q", username, password)
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
})
|
||||
|
||||
if err := svc.Authenticate(context.Background(), "alice", "secret"); !errors.Is(err, ErrInvalidCredentials) {
|
||||
t.Fatalf("expected invalid credentials, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticateReturnsRepoError(t *testing.T) {
|
||||
wanted := errors.New("boom")
|
||||
svc := NewAuthService(fakeAuthRepo{
|
||||
verify: func(ctx context.Context, username, password string) (bool, error) {
|
||||
return false, wanted
|
||||
},
|
||||
})
|
||||
|
||||
if err := svc.Authenticate(context.Background(), "alice", "secret"); !errors.Is(err, wanted) {
|
||||
t.Fatalf("expected repo error, got %v", err)
|
||||
}
|
||||
}
|
||||
@ -84,6 +84,7 @@ class _XWorkmateAppState extends State<XWorkmateApp> {
|
||||
animation: _controller,
|
||||
builder: (context, _) {
|
||||
return MaterialApp(
|
||||
key: const Key('xworkmate-app-shell'),
|
||||
title: kSystemAppName,
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: Locale(_controller.appLanguage.code),
|
||||
|
||||
@ -628,6 +628,7 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
|
||||
),
|
||||
},
|
||||
child: TextField(
|
||||
key: const Key('assistant-input-field'),
|
||||
controller: widget.inputController,
|
||||
focusNode: widget.focusNode,
|
||||
autofocus: true,
|
||||
@ -789,8 +790,8 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: submitLabel,
|
||||
child: FilledButton(
|
||||
key: const Key('assistant-submit-button'),
|
||||
child: FilledButton(
|
||||
key: const Key('assistant-send-button'),
|
||||
onPressed: connecting
|
||||
? null
|
||||
: connected
|
||||
|
||||
@ -227,7 +227,7 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
key: ValueKey('external-acp-apply-${profile.providerKey}'),
|
||||
key: ValueKey('external-acp-save-${profile.providerKey}'),
|
||||
onPressed: () => saveExternalAcpEndpointInternal(
|
||||
controller,
|
||||
settings,
|
||||
|
||||
@ -64,7 +64,7 @@ SPEC CHECKSUMS:
|
||||
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||
mobile_scanner: 0e365ed56cad24f28c0fd858ca04edefb40dfac3
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
patrol: 5df5d241d7f95f0df12a6906bbf45acb43a1e537
|
||||
patrol: cea8074f183a2a4232d0ebd10569ae05149ada42
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
||||
|
||||
|
||||
12
pubspec.lock
12
pubspec.lock
@ -543,26 +543,26 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: patrol
|
||||
sha256: "32fd0709f3871fa56eb9cd88410e3ca816bfa757122bae806a0f842188acb820"
|
||||
sha256: "7825a6e96a8f0755f68eec600a91a08b19bd0975488a70885b3696f6b65ffc0f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.20.0"
|
||||
version: "4.5.0"
|
||||
patrol_finders:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: patrol_finders
|
||||
sha256: "4a658d7d560de523f92deb3fa3326c78747ca0bf7e7f4b8788c012463138b628"
|
||||
sha256: "9970eac0669a90b20ec7e1bcaabd0475655655998068ca656f4df9f6ec84f336"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
version: "3.2.0"
|
||||
patrol_log:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: patrol_log
|
||||
sha256: "9fed4143980df1e3bbcfa00d0b443c7d68f04f9132317b7698bbc37f8a5a58c5"
|
||||
sha256: a2360db165c34692665c0de146e5157887d6b584fdccca8f141f947a5acf1b2e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "0.8.0"
|
||||
pixel_snap:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -38,7 +38,7 @@ dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
golden_toolkit: ^0.15.0
|
||||
patrol: ^3.13.0
|
||||
patrol: ^4.3.0
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
dependency_overrides:
|
||||
|
||||
BIN
test/golden/goldens/home_golden.png
Normal file
BIN
test/golden/goldens/home_golden.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
test/golden/goldens/login_golden.png
Normal file
BIN
test/golden/goldens/login_golden.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Loading…
Reference in New Issue
Block a user