diff --git a/docs/06-concepts/08-caching.md b/docs/06-concepts/08-caching.md index 3c5b4894..faccbcd1 100644 --- a/docs/06-concepts/08-caching.md +++ b/docs/06-concepts/08-caching.md @@ -1,5 +1,5 @@ --- -description: Cache frequently requested objects in Serverpod using local server memory or a distributed Redis cache to reduce expensive database queries. +description: Caching in Serverpod stores frequently requested objects in local server memory or a distributed Redis cache to reduce expensive database queries. --- # Caching @@ -34,7 +34,7 @@ Future getUserData(Session session, int userId) async { } ``` -There are three caches where you can store your objects. Two caches are local to the server handling the current session, and one is distributed across the server cluster through Redis. There are two variants for the local cache: one regular cache, and a priority cache. Place objects that are frequently accessed in the priority cache. +There are three caches where you can store your objects, all reached through the `Session` object. Two are local to the server handling the current session: a regular cache (`session.caches.local`) and a priority cache (`session.caches.localPrio`) for frequently accessed objects. The third, `session.caches.global`, is distributed across the server cluster through Redis. Depending on the type and number of objects that are cached in the global cache, you may want to specify custom caching rules in Redis. This is currently not handled automatically by Serverpod. @@ -44,7 +44,7 @@ During development, you can run code that uses the global cache without a runnin ### Caching primitive objects -To cache primitive objects, call the `put` method with the object. For the `get`, specify the object type as the generic parameter, just like for `SerializableModel` objects: +To cache primitive objects, call the `put` method with the object. For the `get`, specify the object type as the generic parameter, as for `SerializableModel` objects: ```dart await session.caches.local.put('userCount', 17, lifetime: Duration(minutes: 5)); diff --git a/docs/06-concepts/09-logging.md b/docs/06-concepts/09-logging.md index e5b3aff7..9989a136 100644 --- a/docs/06-concepts/09-logging.md +++ b/docs/06-concepts/09-logging.md @@ -1,5 +1,5 @@ --- -description: Log custom messages, exceptions, and queries in Serverpod using the session log method, with configurable retention, console output, and database storage. +description: Serverpod logging records custom messages, exceptions, and queries via the session log method and stores them in the database, with configurable retention and console output. --- # Logging @@ -21,15 +21,15 @@ session.log( ); ``` -Log entries are stored in the following tables of the database: `serverpod_log` for text messages, `serverpod_query_log` for queries, and `serverpod_session_log` for completed sessions. Optionally, it's possible to pass a log level with the message to filter out messages depending on the server's runtime settings. +Log entries are stored in the following tables of the database: `serverpod_log` for text messages, `serverpod_query_log` for queries, and `serverpod_session_log` for completed sessions. -### Controlling Session Logs with Environment Variables or Configuration Files +## Controlling session logs with environment variables or configuration files You can control whether session logs are written to the database, the console, both, or neither, using environment variables or configuration files. **Environment variables take priority** over configuration file settings if both are provided. For the default values when environment variables are not set, see the [default behavior for session logs](#default-behavior-for-session-logs). -#### Environment Variables +### Environment variables - `SERVERPOD_SESSION_PERSISTENT_LOG_ENABLED`: Controls whether session logs are written to the database. - `SERVERPOD_SESSION_LOG_CLEANUP_INTERVAL`: How often to run the log cleanup job (duration string, e.g. `6h`, `24h`). Set to empty to disable automated purging. @@ -38,7 +38,7 @@ For the default values when environment variables are not set, see the [default - `SERVERPOD_SESSION_CONSOLE_LOG_ENABLED`: Controls whether session logs are output to the console. - `SERVERPOD_SESSION_CONSOLE_LOG_FORMAT`: The format for console logging (`text` or `json`). See [configuration](./configuration). -#### Configuration File Example +### Configuration file example You can also configure logging behavior directly in the configuration file: @@ -53,7 +53,7 @@ sessionLogs: Duration strings for the cleanup interval and retention period use the same format as in [models](./models#supported-default-values): e.g. `30d`, `6h`, `1d 2h 30min`. -### Default Behavior for Session Logs +## Default behavior for session logs By default, session logging behavior depends on whether the project has database support: @@ -72,18 +72,18 @@ If `persistentEnabled` is set to `true` but **no database is configured**, a `St ::: :::info -You can use the companion app **[Serverpod Insights](../tools/insights)** to read, search, and configure the logs. +You can use the companion app **[Serverpod Insights](../tools/insights)** to read, search, and configure the logs. ::: -#### Log retention and automated purging +### Log retention and automated purging Since log entries are stored in the database when persistent logging is enabled, the logs table can grow without bound if not purged. Serverpod can automatically purge logs based on configurable retention policies to prevent unchecked storage growth. -##### Default values +#### Default values -- **Cleanup interval**: 24 hours — the cleanup job runs once per day. -- **Retention period**: 90 days — removes entries older than this. -- **Retention count**: 100,000 entries — removes entries that exceed this count. +- **Cleanup interval**: 24 hours; the cleanup job runs once per day. +- **Retention period**: 90 days; removes entries older than this. +- **Retention count**: 100,000 entries; removes entries that exceed this count. If both time-based (retention period) and count-based (retention count) limits are set, entries are removed if they are either too old or beyond the maximum count, whichever is reached first. @@ -91,7 +91,7 @@ If both time-based (retention period) and count-based (retention count) limits a Automatic cleanup is only available when persistent logging is enabled and the cleanup interval is configured. ::: -##### Customizing retention policies +#### Customizing retention policies All three settings are optional and can be set to `null` to disable the respective policy. If the `cleanupInterval` is set to `null`, no purging will run regardless of the other settings, and log tables can grow without bound until you run cleanup manually or re-enable the interval. diff --git a/docs/06-concepts/19-testing/01-get-started.md b/docs/06-concepts/19-testing/01-get-started.md index ee775388..6b17e8c9 100644 --- a/docs/06-concepts/19-testing/01-get-started.md +++ b/docs/06-concepts/19-testing/01-get-started.md @@ -181,7 +181,7 @@ Go to the server directory and generate the test tools: serverpod generate ``` -The default location for the generated file is `test/integration/test_tools/serverpod_test_tools.dart`. The folder name `test/integration` is chosen to differentiate from unit tests (see the [best practises section](best-practises#unit-and-integration-tests) for more information on this). +The default location for the generated file is `test/integration/test_tools/serverpod_test_tools.dart`. The folder name `test/integration` is chosen to differentiate from unit tests (see the [best practices section](./best-practises#unit-and-integration-tests) for more information on this). The generated file exports a `withServerpod` helper that enables you to call your endpoints directly like regular functions: @@ -210,7 +210,7 @@ A few things to note from the above example: :::tip -The location of the test tools can be changed by changing the `server_test_tools_path` key in `config/generator.yaml`. If you remove the `server_test_tools_path` key, the test tools will stop being generated. +The location of the test tools can be changed by changing the `server_test_tools_path` key in `config/generator.yaml`. If you remove the `server_test_tools_path` key, the test tools will stop being generated. ::: diff --git a/docs/06-concepts/19-testing/02-the-basics.md b/docs/06-concepts/19-testing/02-the-basics.md index c1cb3db4..cef7a338 100644 --- a/docs/06-concepts/19-testing/02-the-basics.md +++ b/docs/06-concepts/19-testing/02-the-basics.md @@ -4,6 +4,8 @@ description: The Serverpod test tools basics, set up scenarios with sessionBuild # The basics +This page covers the core building blocks of the test tools: setting up a session, seeding the database, controlling rollback, and simulating authenticated and unauthenticated sessions. + ## Set up a test scenario The `withServerpod` helper provides a `sessionBuilder` that helps with setting up different scenarios for tests. To modify the session builder's properties, call its `copyWith` method. It takes the following named parameters: @@ -15,7 +17,7 @@ The `withServerpod` helper provides a `sessionBuilder` that helps with setting u The `copyWith` method creates a new unique session builder with the provided properties. This can then be used in endpoint calls (see section [Setting authenticated state](#setting-authenticated-state) for an example). -To build out a `Session` (to use for [database calls](#seeding-the-database) or [pass on to functions](advanced-examples#test-business-logic-that-depends-on-session)), simply call the `build` method: +To build out a `Session` (to use for [database calls](#seeding-the-database) or [pass on to functions](./advanced-examples#test-business-logic-that-depends-on-session)), call the `build` method: ```dart Session session = sessionBuilder.build(); @@ -104,7 +106,7 @@ withServerpod('Given Products endpoint', (sessionBuilder, endpoints) { test('then calling `all` should return all products', () async { final products = await endpoints.products.all(sessionBuilder); expect(products, hasLength(2)); - expect(products.map((p) => p.name), contains(['Apple', 'Banana'])); + expect(products.map((p) => p.name), containsAll(['Apple', 'Banana'])); }); }); ``` @@ -233,7 +235,7 @@ var transactionFuture = session.db.transaction((tx) async { await transactionFuture; ``` -In production, the transaction call will throw if any database exception happened during its execution, _even_ if the exception was first caught inside the transaction. However, in the test tools this will not throw an exception due to how the nested transactions are emulated. Quelling exceptions like this is not best practise, but if the code under test does this setting `rollbackDatabase` to `RollbackDatabase.disabled` will ensure the code behaves like in production. +In production, the transaction call will throw if any database exception happened during its execution, _even_ if the exception was first caught inside the transaction. However, in the test tools this will not throw an exception due to how the nested transactions are emulated. Quelling exceptions like this is not best practice, but if the code under test does this setting `rollbackDatabase` to `RollbackDatabase.disabled` will ensure the code behaves like in production. ## Test exceptions @@ -262,8 +264,8 @@ For example, if depending on a generator function to execute up to its `yield`, event queue can be flushed to ensure the generator has executed up to that point: ```dart -var stream = endpoints.someEndoint.generatorFunction(session); +var stream = endpoints.someEndpoint.generatorFunction(session); await flushEventQueue(); ``` -See also [this complete example](advanced-examples#multiple-users-interacting-with-a-shared-stream). +See also [this complete example](./advanced-examples#multiple-users-interacting-with-a-shared-stream). diff --git a/docs/06-concepts/19-testing/03-advanced-examples.md b/docs/06-concepts/19-testing/03-advanced-examples.md index 87375ef0..0ad02d23 100644 --- a/docs/06-concepts/19-testing/03-advanced-examples.md +++ b/docs/06-concepts/19-testing/03-advanced-examples.md @@ -4,6 +4,8 @@ description: Advanced Serverpod testing examples, run integration and unit tests # Advanced examples +These examples build on [the basics](./the-basics) and cover less common testing needs: separating unit and integration tests, testing business logic directly, multi-user stream interactions, and managing database connections. + ## Run unit and integration tests separately To run unit and integration tests separately, the `"integration"` tag can be used as a filter. See the following examples: @@ -19,11 +21,11 @@ dart test -t integration dart test -x integration ``` -To change the name of this tag, see the [`testGroupTagsOverride`](the-basics#configuration) configuration option. +To change the name of this tag, see the [`testGroupTagsOverride`](./the-basics#configuration) configuration option. ## Test business logic that depends on `Session` -It is common to break out business logic into modules and keep it separate from the endpoints. If such a module depends on a `Session` object (e.g to interact with the database), then the `withServerpod` helper can still be used and the second `endpoint` argument can simply be ignored: +It is common to break out business logic into modules and keep it separate from the endpoints. If such a module depends on a `Session` object (e.g to interact with the database), then the `withServerpod` helper can still be used and the second `endpoint` argument can be ignored: ```dart withServerpod('Given decreasing product quantity when quantity is zero', ( @@ -105,20 +107,20 @@ withServerpod('Given CommunicationExampleEndpoint', (sessionBuilder, endpoints) ); var stream = - endpoints.testTools.listenForNumbersOnSharedStream(userSession1); + endpoints.communicationExample.listenForNumbersOnSharedStream(userSession1); // Wait for `listenForNumbersOnSharedStream` to execute up to its // `yield` statement before continuing await flushEventQueue(); - await endpoints.testTools.postNumberToSharedStream(userSession2, 111); - await endpoints.testTools.postNumberToSharedStream(userSession2, 222); + await endpoints.communicationExample.postNumberToSharedStream(userSession2, 111); + await endpoints.communicationExample.postNumberToSharedStream(userSession2, 222); await expectLater(stream.take(2), emitsInOrder([111, 222])); }); }); ``` -## Optimising number of database connections +## Optimizing the number of database connections By default, Dart's test runner runs tests concurrently. The number of concurrent tests depends on the running hosts' available CPU cores. If the host has a lot of cores it could trigger a case where the number of connections to the database exceeds the maximum connections limit set for the database, which will cause tests to fail. diff --git a/docs/06-concepts/19-testing/04-best-practises.md b/docs/06-concepts/19-testing/04-best-practises.md index 9e39a307..a7f4ea51 100644 --- a/docs/06-concepts/19-testing/04-best-practises.md +++ b/docs/06-concepts/19-testing/04-best-practises.md @@ -1,21 +1,20 @@ --- description: Best practices for Serverpod integration testing, import only the generated test tools file, rely on automatic database rollback, and always call endpoints through the provided endpoints object. -# Don't display do's and don'ts in the table of contents toc_max_heading_level: 2 --- -# Best practises +# Best practices ## Imports -While it's possible to import types and test helpers from the `serverpod_test`, it's completely redundant. The generated file exports everything that is needed. Adding an additional import is just unnecessary noise and will likely also be flagged as duplicated imports by the Dart linter. +While it's possible to import types and test helpers from the `serverpod_test` package, it's completely redundant. The generated file exports everything that is needed. Adding an additional import is unnecessary noise and will likely also be flagged as duplicated imports by the Dart linter. ### Don't ```dart import 'serverpod_test_tools.dart'; // Don't import `serverpod_test` directly. -import 'package:serverpod_test/serverpod_test.dart'; ❌ +import 'package:serverpod_test/serverpod_test.dart'; ❌ ``` ### Do @@ -23,12 +22,12 @@ import 'package:serverpod_test/serverpod_test.dart'; ❌ ```dart // Only import the generated test tools file. // It re-exports all helpers and types that are needed. -import 'serverpod_test_tools.dart'; ✅ +import 'serverpod_test_tools.dart'; ✅ ``` -### Database clean up +## Database clean up -Unless configured otherwise, by default `withServerpod` does all database operations inside a transaction that is rolled back after each `test` (see [the configuration options](the-basics#rollback-database-configuration) for more info on this behavior). +Unless configured otherwise, by default `withServerpod` does all database operations inside a transaction that is rolled back after each `test` (see [the configuration options](./the-basics#rollback-database-configuration) for more info on this behavior). ### Don't @@ -40,7 +39,7 @@ withServerpod('Given ProductsEndpoint', (sessionBuilder, endpoints) { await Product.db.insertRow(session, Product(name: 'Apple', price: 10)); }); - tearDown(() async { + tearDown(() async { await Product.db.deleteWhere( ❌ // Unnecessary clean up session, where: (_) => Constant.bool(true), @@ -64,7 +63,7 @@ withServerpod('Given ProductsEndpoint', (sessionBuilder, endpoints) { ✅ // Clean up can be omitted since the transaction is rolled back after each by default // ... -}); +}); ``` ## Calling endpoints @@ -85,8 +84,8 @@ void main() { var session = sessionBuilder.build(); test('when calling `hello` then should return greeting', () async { - // ❌ Don't call and endpoint method directly on the endpoint class. - final greeting = await exampleEndpoint.hello(session, 'Michael'); + // ❌ Don't call an endpoint method directly on the endpoint class. + final greeting = await exampleEndpoint.hello(session, 'Michael'); expect(greeting, 'Hello, Michael!'); }); }); @@ -98,12 +97,10 @@ void main() { ```dart void main() { withServerpod('Given Example endpoint', (sessionBuilder, endpoints) { - var session = sessionBuilder.build(); - test('when calling `hello` then should return greeting', () async { // ✅ Use the provided `endpoints` to call the endpoint that should be tested. final greeting = - await endpoints.example.hello(session, 'Michael'); + await endpoints.example.hello(sessionBuilder, 'Michael'); expect(greeting, 'Hello, Michael!'); }); }); diff --git a/docs/06-concepts/21-security-configuration.md b/docs/06-concepts/21-security-configuration.md index 8fbe873c..ce9c8918 100644 --- a/docs/06-concepts/21-security-configuration.md +++ b/docs/06-concepts/21-security-configuration.md @@ -2,7 +2,9 @@ description: Security configuration in Serverpod lets you enable TLS/SSL directly on the server or configure the client to trust a certificate, using SecurityContextConfig. --- -# Security Configuration +# Security and TLS + +Serverpod can terminate TLS/SSL directly on the server and configure the client to trust your certificate. :::info @@ -11,11 +13,11 @@ However, Serverpod also supports setting up TLS/SSL directly on the server, allo ::: -## Server Security Configuration +## Server security configuration To enable TLS/SSL, pass a `SecurityContextConfig` to the `Serverpod` constructor. -### Dart Configuration Example +### Dart configuration example ```dart final securityContext = SecurityContext() @@ -34,11 +36,11 @@ Serverpod( ); ``` -## Client Security Configuration +## Client security configuration When connecting to a Serverpod server over HTTPS, the client must be configured to trust the server's certificate. -### Dart Configuration Example +### Dart configuration example To enable SSL/TLS, pass a `SecurityContext` to the `Client` constructor. diff --git a/docs/06-concepts/22-experimental.md b/docs/06-concepts/22-experimental.md index a4b5539f..7672aec0 100644 --- a/docs/06-concepts/22-experimental.md +++ b/docs/06-concepts/22-experimental.md @@ -11,7 +11,7 @@ Be cautious when using experimental features in production environments, as thei Experimental features are opt-in additions to Serverpod. They are disabled by default; enable them through the `experimentalFeatures` argument on the `Serverpod` constructor or via `config/generator.yaml`. :::note -To make the LSP server understand the usage of experimental flags and avoid complaints about unknown syntax on model files, configure experimental features in the `config/generator.yaml` file. See the [configuration documentation](configuration#experimental-features) for more details. +To make the LSP server understand the usage of experimental flags and avoid complaints about unknown syntax on model files, configure experimental features in the `config/generator.yaml` file. See the [configuration documentation](./configuration#experimental-features) for more details. ::: ## Experimental internal APIs @@ -36,7 +36,7 @@ The current options you can pass are: ## Exception monitoring -Serverpod allows you to monitor exceptions in a central and flexible way by using the new diagnostic event handlers. +Serverpod allows you to monitor exceptions in a central and flexible way by using diagnostic event handlers. These work both for exceptions thrown in application code and from the framework (e.g. server startup or shutdown errors). This can be used to get all exceptions reported in realtime to services for monitoring and diagnostics, @@ -72,7 +72,7 @@ Example: ### Submitting diagnostic events The API for submitting diagnostic events from user code, e.g. from endpoint methods, web calls, and future calls, -is the new method `submitDiagnosticEvent` under the `experimental` member of the Serverpod class. +is the `submitDiagnosticEvent` method under the `experimental` member of the Serverpod class. ```dart void submitDiagnosticEvent( @@ -102,7 +102,7 @@ class DiagnosticEventTestEndpoint extends Endpoint { ### Guidelines for handlers A `DiagnosticEvent` represents an event that occurs in the server. -`DiagnosticEventHandler` implementations can react to these events +Implementations of `DiagnosticEventHandler` can react to these events in order to gain insights into the behavior of the server. As the name suggests the handlers should perform diagnostics only, @@ -154,19 +154,19 @@ void main() { } ``` -## Shutdown Tasks +## Shutdown tasks -Serverpod provides support for registering **shutdown tasks**—asynchronous operations that are executed when the server is shutting down. This is useful for performing cleanup operations such as saving application state or releasing external resources. +Serverpod provides support for registering **shutdown tasks**: asynchronous operations that run when the server is shutting down. This is useful for performing cleanup operations such as saving application state or releasing external resources. Shutdown tasks are executed _after_ the server has stopped accepting new requests, but _before_ the Redis and database connections are closed. All registered shutdown tasks are executed concurrently, and the server waits for all tasks to complete before fully shutting down. If any task fails, the error is logged, but it does not prevent the server from shutting down. -### Managing Shutdown Tasks +### Managing shutdown tasks To manage shutdown tasks, use the `experimental.shutdownTasks` API on your `Serverpod` instance. This API offers methods for adding and removing tasks. -#### Add Shutdown Task +#### Add a shutdown task Each shutdown task is identified by a unique `Object` identifier and executes a function that returns a `Future`. @@ -185,9 +185,9 @@ serverpod.experimental.shutdownTasks.addTask( ``` -In the example above, a task is added with the identifier `taskIdentifier`. This identifier is used for logging any errors that occur during task execution. +In the example above, a task is added with the identifier `#taskIdentifier`. This identifier is used for logging any errors that occur during task execution. -#### Remove Shutdown task +#### Remove a shutdown task To remove a shutdown task, use the `removeTask` method: @@ -195,4 +195,4 @@ To remove a shutdown task, use the `removeTask` method: serverpod.experimental.shutdownTasks.removeTask(#taskIdentifier); ``` -This will remove the previously registered task associated with `taskIdentifier`. +This will remove the previously registered task associated with `#taskIdentifier`.