ワンクリックで
test-coverage
Run tests and check coverage for Auth9 backend, frontend, and SDK with mock-based testing patterns.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Run tests and check coverage for Auth9 backend, frontend, and SDK with mock-based testing patterns.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Run E2E tests for Auth9 portal using Playwright with hybrid testing strategy.
Run tests, check logs, and troubleshoot Auth9 services in Docker and Kubernetes environments.
Run performance benchmarks for auth9-core API using hey load testing tool.
Execute scenario-based QA testing with browser automation, database validation, and automatic ticket creation for failures.
Reset Auth9 local Docker development environment to a clean state.
Rust coding conventions and patterns for auth9-core development.
SOC 職業分類に基づく
| name | test-coverage |
| description | Run tests and check coverage for Auth9 backend, frontend, and SDK with mock-based testing patterns. |
Run tests and check coverage for Auth9 project (backend, frontend, SDK).
cd auth9-core
cargo test
All tests run fast (~1-2 seconds) with no Docker or external services required.
Use make coverage (wraps cargo-llvm-cov with exclusions):
cd auth9-core
make coverage # Text summary
make coverage-html # HTML report in target/llvm-cov/html/
make coverage-json # JSON output
Why llvm-cov? tarpaulin has known issues with
#[tonic::async_trait]macros (reports ~9% instead of actual ~87% for gRPC code).cargo-llvm-covuses LLVM's instrumentation and correctly tracks async trait code.
Install if needed:
cargo install cargo-llvm-cov
The following files are excluded from coverage tracking:
| File/Directory | Reason |
|---|---|
repository/*.rs | Thin data mapping layer (ORM-equivalent), no business logic to test |
main.rs | Program entry point |
migration/*.rs | Database migration scripts |
These exclusions are configured in auth9-core/Makefile. Repository layer coverage is low by design - all business logic lives in the service layer, which is tested via mock repositories.
cd sdk
pnpm run --filter @auth9/core test
pnpm run --filter @auth9/node test
Or run all SDK tests at once:
cd sdk && pnpm test
cd sdk
pnpm run --filter @auth9/core test:coverage
pnpm run --filter @auth9/node test:coverage
| File/Pattern | Reason |
|---|---|
src/index.ts | Barrel re-export, no logic |
src/types/**/*.ts (@auth9/core only) | Pure type definitions, no runtime code |
These exclusions are configured in each package's vitest.config.ts.
sdk/packages/
├── core/src/
│ ├── utils.test.ts # toSnakeCase / toCamelCase (12 tests)
│ ├── errors.test.ts # Error hierarchy, createErrorFromStatus (17 tests)
│ ├── http-client.test.ts # HTTP client with fetch mocking (8 tests)
│ └── claims.test.ts # getTokenType() discrimination (3 tests)
└── node/src/
├── testing.test.ts # createMockToken, createMockAuth9 (5 tests)
└── client-credentials.test.ts # Token fetch, caching, cache clear (3 tests)
SDK tests use vitest with global fetch mocking (no external services):
import { describe, it, expect, vi, beforeEach } from "vitest";
// Mock global fetch
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
beforeEach(() => {
mockFetch.mockReset();
});
it("fetches data with auto snake/camel conversion", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({ data: { user_name: "test" } }),
});
const client = new Auth9HttpClient({ baseUrl: "http://localhost:8080" });
const result = await client.get<{ userName: string }>("/api/v1/users/1");
expect(result.userName).toBe("test"); // auto camelCase
});
cd auth9-portal
npx vitest --run
cd auth9-portal
npm run test:coverage
Auth9-core tests use mocks instead of real services:
| Component | Testing Approach |
|---|---|
| Repository layer | Mock traits with mockall |
| Service layer | Unit tests with mock repositories |
| gRPC services | NoOpCacheManager + mock repositories |
| External HTTP (OIDC) | wiremock HTTP mocking |
NoOpCacheManagerauth9-core/
├── src/
│ ├── service/
│ │ ├── tenant.rs # Service + unit tests (#[cfg(test)])
│ │ ├── user.rs # Service + unit tests
│ │ ├── client.rs # Service + unit tests
│ │ └── rbac.rs # Service + unit tests
│ └── repository/
│ └── *.rs # Traits with #[mockall::automock]
└── tests/
├── api_test.rs # Entry point for api/ and grpc/ tests
├── api/
│ ├── mod.rs # Test repositories (TestUserRepository, etc.)
│ └── http/ # HTTP handler tests
│ ├── mod.rs # TestAppState, build_test_router
│ └── *_http_test.rs # Handler tests
├── grpc/
│ ├── mod.rs # GrpcTestBuilder, MockCacheManager
│ ├── exchange_token_test.rs # exchange_token tests
│ ├── validate_token_test.rs # validate_token tests
│ ├── get_user_roles_test.rs # get_user_roles tests
│ └── introspect_token_test.rs # introspect_token tests
└── (wiremock-based HTTP tests inline in service modules)
HTTP handler tests use Dependency Injection via HasServices trait to test production code with mock repositories.
Production: AppState (impl HasServices) → build_router() → Handlers<AppState>
Tests: TestAppState (impl HasServices) → build_router() → Handlers<TestAppState>
↑
Same production handlers!
| File | Purpose |
|---|---|
src/state.rs | HasServices trait definition |
src/server/mod.rs | AppState + build_router<S: HasServices>() |
tests/api/http/mod.rs | TestAppState + test helpers |
tests/api/http/*_http_test.rs | HTTP handler tests |
All handlers use generic <S: HasServices> instead of concrete AppState:
// src/api/tenant.rs
pub async fn list<S: HasServices>(
State(state): State<S>,
Query(pagination): Query<PaginationQuery>,
) -> Result<impl IntoResponse> {
let (tenants, total) = state
.tenant_service() // Method from HasServices trait
.list(pagination.page, pagination.per_page)
.await?;
Ok(Json(PaginatedResponse::new(tenants, pagination.page, pagination.per_page, total)))
}
// tests/api/http/mod.rs
pub struct TestAppState {
pub tenant_service: Arc<TenantService<TestTenantRepository>>,
pub user_service: Arc<UserService<TestUserRepository>>,
// ... other services with test repositories
}
impl HasServices for TestAppState {
type TenantRepo = TestTenantRepository;
type UserRepo = TestUserRepository;
// ... associated types
fn tenant_service(&self) -> &TenantService<Self::TenantRepo> {
&self.tenant_service
}
// ... other methods
}
// tests/api/http/tenant_http_test.rs
use crate::api::http::{build_test_router, get_json, post_json, TestAppState};
#[tokio::test]
async fn test_list_tenants_returns_200() {
let state = TestAppState::new("http://mock-oidc");
// Seed test data
state.tenant_repo.seed_tenant(CreateTenantInput {
name: "Test".to_string(),
slug: "test".to_string(),
..Default::default()
}).await;
// Use production router with TestAppState
let app = build_test_router(state);
let (status, body): (_, Option<PaginatedResponse<Tenant>>) =
get_json(&app, "/api/v1/tenants").await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body.unwrap().data.len(), 1);
}
HasServices trait for new handlers - never use concrete AppStatebuild_test_router() uses build_router(state) from productionTestTenantRepository, TestUserRepository, etc.Service tests live in src/service/*.rs inside #[cfg(test)] modules:
#[cfg(test)]
mod tests {
use super::*;
use crate::repository::tenant::MockTenantRepository;
use mockall::predicate::*;
#[tokio::test]
async fn test_create_tenant_success() {
let mut mock = MockTenantRepository::new();
mock.expect_find_by_slug()
.with(eq("test-tenant"))
.returning(|_| Ok(None));
mock.expect_create()
.returning(|input| Ok(Tenant {
name: input.name.clone(),
slug: input.slug.clone(),
..Default::default()
}));
let service = TenantService::new(Arc::new(mock), None);
let input = CreateTenantInput {
name: "Test Tenant".to_string(),
slug: "test-tenant".to_string(),
logo_url: None,
settings: None,
};
let result = service.create(input).await;
assert!(result.is_ok());
}
}
gRPC tests use NoOpCacheManager instead of real Redis:
use auth9_core::cache::NoOpCacheManager;
fn create_test_cache() -> NoOpCacheManager {
NoOpCacheManager::new()
}
#[tokio::test]
async fn test_exchange_token() {
let cache_manager = create_test_cache();
let grpc_service = TokenExchangeService::new(
jwt_manager,
cache_manager, // No Redis needed
user_repo,
service_repo,
rbac_repo,
);
// ...
}
Use wiremock to mock external HTTP endpoints (e.g. OIDC discovery):
use wiremock::{Mock, ResponseTemplate, matchers::{method, path}};
#[tokio::test]
async fn test_oidc_operation() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/.well-known/openid-configuration"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"issuer": mock_server.uri(),
"token_endpoint": format!("{}/token", mock_server.uri())
})))
.mount(&mock_server)
.await;
// Use mock_server.uri() as OIDC provider URL
}
All test coverage targets in this skill follow the project minimum requirement: >=90%.
| Layer | Target | Notes |
|---|---|---|
| Domain/Business logic | >=90% | Pure validation, no I/O |
| Service layer | >=90% | Core business logic |
| API handlers | >=90% | HTTP routing |
| gRPC handlers | >=90% | Token exchange, validation |
| Repository layer | N/A | Excluded from tracking (thin data mapping) |
| SDK @auth9/core | >=90% | Utils, errors, HTTP client |
| SDK @auth9/node | >=90% | Token verifier, gRPC client, middleware, testing |
cargo clean firstcargo llvm-cov instead (tarpaulin can't track #[async_trait] macros properly)cargo install cargo-llvm-cov