Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/06-concepts/08-caching.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -34,7 +34,7 @@ Future<UserData> 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.

Expand All @@ -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));
Expand Down
26 changes: 13 additions & 13 deletions docs/06-concepts/09-logging.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -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:

Expand All @@ -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:

Expand All @@ -72,26 +72,26 @@ 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.

:::note
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.

Expand Down
4 changes: 2 additions & 2 deletions docs/06-concepts/19-testing/01-get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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.

:::

Expand Down
12 changes: 7 additions & 5 deletions docs/06-concepts/19-testing/02-the-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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();
Expand Down Expand Up @@ -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']));
});
});
```
Expand Down Expand Up @@ -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.
<!-- markdownlint-enable MD029 -->

## Test exceptions
Expand Down Expand Up @@ -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).
14 changes: 8 additions & 6 deletions docs/06-concepts/19-testing/03-advanced-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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', (
Expand Down Expand Up @@ -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.

Expand Down
25 changes: 11 additions & 14 deletions docs/06-concepts/19-testing/04-best-practises.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
---
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

```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

Expand All @@ -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),
Expand All @@ -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
Expand All @@ -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!');
});
});
Expand All @@ -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!');
});
});
Expand Down
12 changes: 7 additions & 5 deletions docs/06-concepts/21-security-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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()
Expand All @@ -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.

Expand Down
Loading
Loading