From c5101a52fd3c199ce953af7bd7346664037a96d2 Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Wed, 9 Sep 2026 09:41:51 +0200 Subject: [PATCH] Close #67: List Talk Conversations --- AGENTS.md | 1 + README.md | 10 +- .../CommandLineInterface.md | 2 + .../Documentation.docc/Documentation.md | 18 ++ Sources/Rainmaker/Models/Conversation.swift | 134 ++++++++ .../Rainmaker/Models/ConversationAvatar.swift | 27 ++ .../Rainmaker/Models/ConversationType.swift | 120 +++++++ Sources/Rainmaker/Models/Talk.swift | 61 ++++ .../Models/ConversationsResponse.swift | 24 ++ Sources/Rainmaker/Server.swift | 66 ++++ Sources/Rainmaker/Serving.swift | 50 +++ .../RainmakerCLI/Commands/Conversations.swift | 108 +++++++ Sources/RainmakerCLI/Commands/Rainmaker.swift | 2 +- .../Fixtures/FixtureOrchestrator.swift | 36 ++- .../Fixtures/FixtureProvisioner.swift | 36 +++ .../Fixtures/FixtureRecordingError.swift | 7 + Tests/RainmakerTests/CapabilitiesTests.swift | 14 + .../ConversationsRequestTests.swift | 304 ++++++++++++++++++ Tests/RainmakerTests/ConversationsTests.swift | 89 +++++ .../RainmakerTests/FixtureCanonicalizer.swift | 42 ++- .../FixtureCanonicalizerTests.swift | 45 ++- Tests/RainmakerTests/FixtureLocator.swift | 3 + Tests/RainmakerTests/ModelEncodingTests.swift | 41 +++ Tests/RainmakerTests/NavigationTests.swift | 11 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../api/v1/room/3bpzwdam/avatar/Body.bin | 6 + .../api/v1/room/3bpzwdam/avatar/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../ocs/v2.php/core/navigation/apps/Body.json | 2 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../api/v1/room/smyfkdd6/avatar/Body.bin | 7 + .../api/v1/room/smyfkdd6/avatar/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../ocs/v2.php/core/navigation/apps/Body.json | 2 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../api/v1/room/7nxqtfet/avatar/Body.bin | 7 + .../api/v1/room/7nxqtfet/avatar/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../ocs/v2.php/core/navigation/apps/Body.json | 2 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../ocs/v2.php/cloud/capabilities/Body.json | 2 +- .../api/v1/room/75293txe/avatar/Body.bin | 7 + .../api/v1/room/75293txe/avatar/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../v2.php/apps/spreed/api/v4/room/Body.json | 1 + .../apps/spreed/api/v4/room/Headers.txt | 2 + .../ocs/v2.php/core/navigation/apps/Body.json | 2 +- .../RainmakerTests/URLRecordingSession.swift | 4 +- 61 files changed, 1302 insertions(+), 36 deletions(-) create mode 100644 Sources/Rainmaker/Models/Conversation.swift create mode 100644 Sources/Rainmaker/Models/ConversationAvatar.swift create mode 100644 Sources/Rainmaker/Models/ConversationType.swift create mode 100644 Sources/Rainmaker/Models/Talk.swift create mode 100644 Sources/Rainmaker/Responses/Models/ConversationsResponse.swift create mode 100644 Sources/RainmakerCLI/Commands/Conversations.swift create mode 100644 Tests/RainmakerTests/ConversationsRequestTests.swift create mode 100644 Tests/RainmakerTests/ConversationsTests.swift create mode 100644 Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Body.bin create mode 100644 Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Body.bin create mode 100644 Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Body.bin create mode 100644 Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Body.bin create mode 100644 Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt create mode 100644 Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json create mode 100644 Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt diff --git a/AGENTS.md b/AGENTS.md index 4aa2eba..486a25f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,6 +45,7 @@ The human readable introduction of this Swift Package is in [README.md](README.m - The fixtures are regenerated by the `record-fixtures` subcommand of `rainmaker-cli`, which requires macOS and a running Docker daemon. It is never run in continuous integration. - Run `swift run rainmaker-cli record-fixtures` to record every supported version, or scope it, e.g. `swift run rainmaker-cli record-fixtures --version 32.0.3 --filter ListingTests`. - The subcommand deploys a Nextcloud container per version, provisions a baseline, records each test against it (writing into `Tests/RainmakerTests/Responses/`), and finally replays the captures with `swift test` to prove they work without a server. Review the result with `git diff` before committing. +- Two apps the fixtures depend on are not part of a Nextcloud installation and are installed into every container from the app store, declared as `FixtureOrchestrator.installedApps`: the notes app for `NotesTests` and the Talk app (`spreed`) for `ConversationsTests`. That is why the recorded capabilities and navigation fixtures advertise `notes` and `spreed`, and it means recording needs the app store to be reachable. It also means the fixtures of those two suites track a release of their app rather than of the server, which is why their assertions rest on state `FixtureProvisioner` seeds rather than on whatever the app creates by itself. - A handful of tests assume a server state different from the baseline. Those preconditions live in `FixtureProvisioner.applyPrecondition(forTest:)`. When the verification pass reports a test failing after recording, add or adjust its precondition there. - Adding a server version is a single edit: add a case to `ServerVersion` in the `RainmakerTestServerTags` module. Both the test parameterization and the recorder's default version list derive from it (or pass `--version` to scope a run). diff --git a/README.md b/README.md index b81d971..c60fb7b 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ [![Tests](https://github.com/i2h3/rainmaker/actions/workflows/test.yml/badge.svg)](https://github.com/i2h3/rainmaker/actions/workflows/test.yml) [![REUSE](https://api.reuse.software/badge/github.com/i2h3/rainmaker)](https://api.reuse.software/info/github.com/i2h3/rainmaker) -A simple Swift library and CLI to access [Nextcloud](https://www.nextcloud.com) programmatically: files, notifications, activities, notes and collectives. +A simple Swift library and CLI to access [Nextcloud](https://www.nextcloud.com) programmatically: files, notifications, activities, notes, collectives and Talk conversations. For further information, see [the documentation which is built from the source code and deployed to GitHub pages](https://i2h3.github.io/rainmaker/). ## Testing -The test suite runs entirely against static fixtures committed under `Tests/RainmakerTests/Responses/`, so `swift test` needs no server and runs on every platform. +The test suite uses static fixtures committed under `Tests/RainmakerTests/Responses/` and isolated request doubles, so `swift test` needs no server and runs on every platform. -These fixtures are generated automatically by the `record-fixtures` subcommand of `rainmaker-cli` (macOS with Docker only, never run in CI). It deploys ephemeral Nextcloud containers via [NextcloudContainerManager](https://github.com/i2h3/nextcloud-container-manager), records the test suite against them, and verifies the captures replay without a server: +Most of these fixtures are generated automatically by the `record-fixtures` subcommand of `rainmaker-cli` (macOS with Docker only, never run in CI). It deploys ephemeral Nextcloud containers via [NextcloudContainerManager](https://github.com/i2h3/nextcloud-container-manager), installs the apps whose fixtures depend on them from the Nextcloud app store, records the test suite against them, and verifies the captures replay without a server: ```bash # Regenerate fixtures for all supported versions, then review and commit the diff. @@ -24,7 +24,9 @@ swift run rainmaker-cli record-fixtures git diff Tests/RainmakerTests/Responses ``` -Scope a run with `--version ` and `--filter `. See [AGENTS.md](AGENTS.md) for details. +Scope a run with `--version ` and `--filter `. +A few suites keep hand-authored fixtures instead, because a plain container cannot reproduce the responses they need; `recordableSuites` in `Sources/RainmakerCLI/Fixtures/FixtureOrchestrator.swift` lists which suites record and why the others do not. +See [AGENTS.md](AGENTS.md) for details. ## License diff --git a/Sources/Rainmaker/Documentation.docc/CommandLineInterface.md b/Sources/Rainmaker/Documentation.docc/CommandLineInterface.md index 8c93314..81e9227 100644 --- a/Sources/Rainmaker/Documentation.docc/CommandLineInterface.md +++ b/Sources/Rainmaker/Documentation.docc/CommandLineInterface.md @@ -18,6 +18,7 @@ SUBCOMMANDS: activity-filters List the filters the server offers to narrow the activity stream down with. Requires authentication and the server's activity app. capabilities Fetch the capabilities advertised by a server. Authentication is optional. collectives List the collectives of the authenticated user and their pages. Requires authentication and the server's collectives app. + conversations List the Talk conversations of the authenticated user and retrieve their images. Requires authentication and the server's Talk app. create-directory Create a directory on the server. delete Delete a file or directory from the server. delete-app-password Delete the app password currently used to authenticate, ending the session on the server side. @@ -31,6 +32,7 @@ SUBCOMMANDS: notes-settings Show where the notes app stores the notes of the authenticated user. Requires authentication and the server's notes app. notifications List the notifications queued for the authenticated user. Requires authentication and the server's notifications app. poll Poll the status of a previously initiated login flow. + trash Manage the server trash bin. upload Upload a file or directory to a folder on the server. watch Observe server-side changes over notify_push (or polling when unavailable) and print each event. Runs until interrupted. diff --git a/Sources/Rainmaker/Documentation.docc/Documentation.md b/Sources/Rainmaker/Documentation.docc/Documentation.md index 1120288..d7756f9 100644 --- a/Sources/Rainmaker/Documentation.docc/Documentation.md +++ b/Sources/Rainmaker/Documentation.docc/Documentation.md @@ -91,6 +91,23 @@ Pages are returned flat and in the server's order; their hierarchy is reconstruc - ``CollectivePage`` - ``MembershipLevel`` +### Talk + +Retrieve the conversations of an account and the image of a single one of them, which is what a client needs to surface conversations without taking part in them. +The Talk app is not part of a Nextcloud installation, and whether it is available is advertised through the ``Talk`` capability, which unlike every other capability covered here is visible to anonymous clients as well. +Conversations come back in the server's own order, which is no order at all, so a client presenting a list sorts them by ``Conversation/lastActivity`` itself. +The image of a conversation is whatever the server resolved it to, from an uploaded picture to an icon it generated, and is commonly an SVG document rather than a bitmap. +Each explicit avatar fetch bypasses the local HTTP cache. +Cache images between displays, refreshing when ``Conversation/avatarVersion`` changes and periodically even if it does not, for example once a day. +In one-to-one conversations that version says nothing at all: it is the same value for every such conversation and never moves when the other person changes their profile picture. +Keep separate cached images for each server, account, conversation token and appearance. + +- ``Server/conversations()`` +- ``Server/conversationAvatar(_:darkTheme:)`` +- ``Conversation`` +- ``ConversationType`` +- ``ConversationAvatar`` + ### Apps Navigation List the server apps, such as Files, Photos and Activity, which the server advertises to the authenticated user so that a client can surface them in its own navigation. @@ -106,6 +123,7 @@ List the server apps, such as Files, Photos and Activity, which the server adver - ``Notes`` - ``Notifications`` - ``PushNotifications`` +- ``Talk`` - ``Theming`` - ``Trashing`` - ``Version`` diff --git a/Sources/Rainmaker/Models/Conversation.swift b/Sources/Rainmaker/Models/Conversation.swift new file mode 100644 index 0000000..b2f387d --- /dev/null +++ b/Sources/Rainmaker/Models/Conversation.swift @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import Foundation + +/// +/// A single Nextcloud Talk conversation the authenticated user takes part in. +/// +/// Conversations are listed through ``Server/conversations()`` and their image is retrieved through ``Server/conversationAvatar(_:darkTheme:)``. They are provided by the Talk app (`spreed`) which, like the notes app, is not part of a Nextcloud installation and has to be installed separately. Whether it is available can be checked in advance via the ``Talk`` capability, e.g. `try await capabilities().contains(Talk.self)`. +/// +/// The server's own API calls a conversation a "room", which is why the endpoint behind ``Server/conversations()`` is `apps/spreed/api/v4/room`, while its user interface calls it a conversation. This type follows the latter. +/// +/// Only ``token`` addresses a conversation in the server's API. ``id`` is a plain number unique per server and is what makes this type `Identifiable`, but every other Talk endpoint takes the token in its path instead. +/// +/// The server sends far more about a conversation than is modelled here, because this type exists to list conversations rather than to take part in one. The last message, the read markers (`lastReadMessage`, `lastCommonReadMessage`), the permission bit fields (`permissions`, `attendeePermissions`, `defaultPermissions`, `callPermissions`), the attendee and session details (`attendeeId`, `actorId`, `actorType`, `sessionId`, `lastPing`), the call state (`hasCall`, `callFlag`, `callStartTime`, `callRecording`, `canStartCall`), `isCustomAvatar`, the lobby, breakout room, message expiration, SIP and federation fields as well as `name`, `description`, `listable`, `notificationLevel`, `participantType`, `hasPassword`, `isFavorite`, `isArchived`, `isImportant`, `isSensitive` and `tagIds` are intentionally not modelled, as chatting, calling and moderating are out of scope. A client needing any of them can build its own request with ``Server/makeOCSRequest(for:method:queryItems:)``. +/// +public struct Conversation: Model, Identifiable, CustomStringConvertible, CustomDebugStringConvertible, Decodable { + /// + /// The server-assigned identifier of the conversation, unique per server. + /// + /// This is not what addresses a conversation in the server's API; ``token`` is. It is modelled all the same because it is what makes a conversation `Identifiable` and what a client can key its own storage by. + /// + public let id: Int + + /// + /// The opaque token identifying the conversation, e.g. `"oypsiy6j"`. + /// + /// Every Talk endpoint beyond the listing takes this in its path, which is why it is also what ``Server/conversationAvatar(_:darkTheme:)`` and any request built with ``Server/makeOCSRequest(for:method:queryItems:)`` are given. + /// + public let token: String + + /// + /// What kind of conversation this is. + /// + public let type: ConversationType + + /// + /// The name to present to the user. + /// + /// The server resolves this for the authenticated user, so a ``ConversationType/oneToOne`` conversation carries the other person's name even though the conversation itself is stored without one. It is localized to the account's language for the conversations the server maintains on its own, such as ``ConversationType/changelog`` and ``ConversationType/noteToSelf``. + /// + public let displayName: String + + /// + /// The server's version marker for the conversation's avatar, e.g. `"b0135871"`. + /// + /// A change invalidates a cached image, but an unchanged marker is no promise that the image is unchanged, and for a ``ConversationType/oneToOne`` conversation it promises nothing at all. + /// The server derives the marker of such a conversation from the path of a generic icon, which makes it one and the same value for every one-to-one conversation on that server, while the avatar endpoint answers with the other person's current profile picture. Nothing that person does to their picture moves this value. + /// + /// So treat this as a hint to invalidate with, never as a key to cache under. Cache images between displays, refreshing them when this marker changes and periodically even when it does not, for example once a day. + /// Keep separate entries for each server, account, conversation token and appearance. The appearance belongs in the key because the marker of an emoji avatar is derived from its light variant alone, so both appearances of one conversation share a single value. + /// ``Server/conversationAvatar(_:darkTheme:)`` bypasses the local HTTP cache so that an explicit refresh can retrieve updated bytes. + /// + public let avatarVersion: String + + /// + /// The moment of the last activity in the conversation. + /// + /// This corresponds to the server's `lastActivity` field, a number of whole seconds since the Unix epoch in the UTC time zone. + /// The conversion happens in ``init(from:)`` rather than through a decoder's date strategy, so that this type decodes correctly no matter which `JSONDecoder` a downstream project passes it to. + /// + /// A conversation without any activity yet is reported as `0` by the server and therefore surfaces as `Date(timeIntervalSince1970: 0)` rather than as `nil`, which still sorts as the oldest. + /// + public let lastActivity: Date + + /// + /// The number of chat messages in the conversation the authenticated user has not read yet. + /// + public let unreadMessages: Int + + /// + /// Whether the authenticated user was mentioned in the conversation since they last visited it. + /// + /// This includes a mention of everyone rather than only a direct one, which the server reports separately in a field this type does not model. + /// + public let unreadMention: Bool + + /// + /// The keys a conversation is decoded from, which are the names the server sends. + /// + /// Unlike ``Note`` or ``Collective``, this type needs no separate set of encoding keys: no property is renamed here, so the two would be identical and the synthesized `Encodable` conformance already encodes under the property names. + /// + private enum CodingKeys: String, CodingKey { + case id + case token + case type + case displayName + case avatarVersion + case lastActivity + case unreadMessages + case unreadMention + } + + // MARK: - Decodable + + /// + /// Decode a conversation from the server's payload. + /// + /// Every modelled field is required: the listing endpoint declares all of them for every server version this library supports, so a missing one means a response this type cannot describe. Whatever else the payload carries is ignored, which is what lets one client read the conversations of servers running different releases of the Talk app. + /// + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + id = try container.decode(Int.self, forKey: .id) + token = try container.decode(String.self, forKey: .token) + type = try container.decode(ConversationType.self, forKey: .type) + displayName = try container.decode(String.self, forKey: .displayName) + avatarVersion = try container.decode(String.self, forKey: .avatarVersion) + unreadMessages = try container.decode(Int.self, forKey: .unreadMessages) + unreadMention = try container.decode(Bool.self, forKey: .unreadMention) + + // Decoding into a `TimeInterval` rather than an integer avoids the trap an out of range value would cause on the platforms where `Int` is only 32 bits wide. + let secondsSince1970 = try container.decode(TimeInterval.self, forKey: .lastActivity) + lastActivity = Date(timeIntervalSince1970: secondsSince1970) + } + + // MARK: - CustomStringConvertible + + /// + /// Implementation for `CustomStringConvertible` conformance to have a concise and human-readable textual representation of a conversation. + /// + public var description: String { + displayName + } + + // MARK: - CustomDebugStringConvertible + + /// + /// Implementation for `CustomDebugStringConvertible` conformance to have a concise and human-readable textual representation of a conversation. + /// + public var debugDescription: String { + "#\(id) (\(token)): \(displayName)" + } +} diff --git a/Sources/Rainmaker/Models/ConversationAvatar.swift b/Sources/Rainmaker/Models/ConversationAvatar.swift new file mode 100644 index 0000000..4d99562 --- /dev/null +++ b/Sources/Rainmaker/Models/ConversationAvatar.swift @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import Foundation + +/// +/// The image of a Nextcloud Talk conversation as the server resolved it. +/// +/// This is what ``Server/conversationAvatar(_:darkTheme:)`` returns. The server decides which image a conversation has, so a client renders whatever comes back rather than deciding anything itself: it is a picture a moderator uploaded, an emoji picked in the web interface, the other person's avatar in a ``ConversationType/oneToOne`` conversation, or an icon the server generates from the kind of conversation it is. +/// +/// > Important: The image is commonly an SVG document rather than a bitmap, because every generated icon and every emoji avatar is one. Anything turning these bytes into a platform image has to handle that, which is what ``contentType`` is for. +/// +/// Whether the image was set by a user or generated by the server is deliberately not reported: the response carries no such signal, and the only field which does is the conversation's `isCustomAvatar`, which ``Conversation`` does not model. +/// +public struct ConversationAvatar: Model { + /// + /// The bytes of the image exactly as the server sent them. + /// + public let data: Data + + /// + /// The MIME type of ``data``, e.g. `"image/svg+xml"` or `"image/png"`. + /// + /// This is the response's `Content-Type` and the only description of what the bytes are, which is why ``Server/conversationAvatar(_:darkTheme:)`` treats its absence as a failure rather than guessing. + /// + public let contentType: String +} diff --git a/Sources/Rainmaker/Models/ConversationType.swift b/Sources/Rainmaker/Models/ConversationType.swift new file mode 100644 index 0000000..0ace6eb --- /dev/null +++ b/Sources/Rainmaker/Models/ConversationType.swift @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import Foundation + +/// +/// The kind of a Nextcloud Talk conversation. +/// +/// This is the ``Conversation/type`` of a conversation as listed by ``Server/conversations()``, and it is what tells a conversation between two people apart from a group, from a public link conversation, and from the ones the server maintains on its own. +/// +/// The kinds are those of the Talk app, whose raw values are neither consecutive nor self-explanatory, which is why they are modelled as cases rather than surfaced as the plain number the server sends. +/// +public enum ConversationType: Model, Decodable, Equatable { + /// + /// A conversation between the authenticated user and exactly one other person, equalling the raw representation as `1`. + /// + /// Such a conversation is named by the server rather than by its participants, so its ``Conversation/displayName`` carries the other person's name. + /// + case oneToOne + + /// + /// A conversation between a group of people, equalling the raw representation as `2`. + /// + case group + + /// + /// A conversation which can be joined through a public link, equalling the raw representation as `3`. + /// + /// The case is not named `public` because that is a Swift keyword, and a name needing backticks at every declaration is worse than a slightly longer one. + /// + case publicConversation + + /// + /// The conversation the Talk app keeps its own release notes in, equalling the raw representation as `4`. + /// + /// Every account has one, it is read only, and it is created without the user asking for it, which is why a client presenting a conversation list may well want to leave it out. + /// + case changelog + + /// + /// What is left of a ``oneToOne`` conversation after the other person's account was removed, equalling the raw representation as `5`. + /// + case formerOneToOne + + /// + /// The conversation an account has with itself, equalling the raw representation as `6`. + /// + /// The server creates it on demand and identifies it through the `note_to_self` object type. + /// + case noteToSelf + + /// + /// A kind this library does not know about, carrying the raw representation the server sent. + /// + /// This exists so that a kind introduced by a future version of the Talk app cannot fail the decoding of a whole conversation, and with it of the entire listing. Anything switching over this enum has to expect it. The server's own marker for a kind it cannot name is `-1`, which lands here as well. + /// + case other(Int) + + /// + /// Derive a case from the raw value returned by the server. + /// + /// Every unknown value becomes ``other(_:)`` rather than failing, mirroring how ``MembershipLevel/init(_:)`` absorbs the raw values of a membership level. + /// + public init(_ rawValue: Int) { + switch rawValue { + case 1: + self = .oneToOne + case 2: + self = .group + case 3: + self = .publicConversation + case 4: + self = .changelog + case 5: + self = .formerOneToOne + case 6: + self = .noteToSelf + default: + self = .other(rawValue) + } + } + + // MARK: - Decodable + + /// + /// Decode a kind from the plain number the server sends for it. + /// + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + try self.init(container.decode(Int.self)) + } + + // MARK: - Encodable + + /// + /// Encode a kind as the name of its case, so that the encoded form is readable rather than a number whose meaning has to be looked up. + /// + /// A kind this library does not know about has no name to encode, so ``other(_:)`` falls back to the raw number the server sent. This asymmetry mirrors ``MembershipLevel/encode(to:)``, which likewise encodes its named cases as strings and its only value-carrying case as a number. + /// + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .oneToOne: + try container.encode("oneToOne") + case .group: + try container.encode("group") + case .publicConversation: + try container.encode("publicConversation") + case .changelog: + try container.encode("changelog") + case .formerOneToOne: + try container.encode("formerOneToOne") + case .noteToSelf: + try container.encode("noteToSelf") + case let .other(type): + try container.encode(type) + } + } +} diff --git a/Sources/Rainmaker/Models/Talk.swift b/Sources/Rainmaker/Models/Talk.swift new file mode 100644 index 0000000..ec26367 --- /dev/null +++ b/Sources/Rainmaker/Models/Talk.swift @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import Foundation + +/// +/// The server's Talk capability, advertised when the Talk app is installed and enabled. +/// +/// On Nextcloud, conversations are provided by the "Talk" app which, unlike most of what this library covers, is not part of a Nextcloud installation and has to be installed separately. When that app is available the server advertises this object under the `spreed` key, which is why ``key`` is `"spreed"` — the app's own internal identifier, which predates the name it is presented under. Its mere presence is the signal a client needs before calling ``Server/conversations()``: check it with `try await capabilities().contains(Talk.self)`. +/// +/// Unlike the ``Notes``, ``Notifications`` and ``Activity`` capabilities, this object is advertised to anonymous clients as well, so its presence says that the app is installed on the server rather than that the authenticated user may use it. +/// +/// Talk versions its endpoints in their paths rather than advertising an API version, so there is nothing to check here the way ``Notes/isSupported`` checks the notes API. What a given server can do is described by named feature flags instead, which ``supports(_:)`` looks up. +/// All fields are kept optional so that a server which omits one of them still decodes successfully. +/// +/// The app's configuration, which the server advertises under `config` and `config-local`, is deliberately not modelled: it is deeply nested, it is reshaped between Talk releases, and none of it is needed to list conversations. A downstream project which needs part of it can declare its own ``Capability`` with the same ``key`` modelling just that subtree, which is exactly the extension point ``Capability`` offers. +/// +public struct Talk: Capability { + public static let key = "spreed" + + /// + /// The features the server supports, including those a conversation hosted on another server can offer, e.g. `["audio", "video", "chat-v2", "conversation-v4"]`. + /// + /// This corresponds to the server's `features` field. Look a flag up through ``supports(_:)`` rather than reaching in here, so that ``localFeatures`` is considered as well. + /// + public let features: [String]? + + /// + /// The features which only conversations hosted by this server itself offer, e.g. `["favorites", "note-to-self"]`. + /// + /// This corresponds to the server's `features-local` field. A conversation reached through federation is hosted elsewhere and does not necessarily offer what this lists, which is why the server keeps the two apart. + /// + public let localFeatures: [String]? + + /// + /// The version of the Talk app itself, e.g. `"24.0.4"`. + /// + /// This is the app's own version and unrelated to the server ``Version``, which is why it is kept as the plain string the server sends. Prefer ``supports(_:)`` over comparing versions: a feature flag says what a server can actually do, a version only implies it. + /// + public let version: String? + + /// + /// Whether the server advertises the given Talk feature. + /// + /// The flag is looked up in ``features`` as well as in ``localFeatures``, which together describe everything a conversation on this server can do. + /// + /// - Parameters: + /// - feature: The feature flag to check for, e.g. `"conversation-v4"`. + /// + /// - Returns: `true` when either list contains the flag, otherwise `false`. + /// + public func supports(_ feature: String) -> Bool { + features?.contains(feature) == true || localFeatures?.contains(feature) == true + } + + private enum CodingKeys: String, CodingKey { + case features + case localFeatures = "features-local" + case version + } +} diff --git a/Sources/Rainmaker/Responses/Models/ConversationsResponse.swift b/Sources/Rainmaker/Responses/Models/ConversationsResponse.swift new file mode 100644 index 0000000..4ce8481 --- /dev/null +++ b/Sources/Rainmaker/Responses/Models/ConversationsResponse.swift @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import Foundation + +/// +/// This is the JSON response as returned by the server for `GET /ocs/v2.php/apps/spreed/api/v4/room`. +/// +/// This is a data transfer object covering the OCS envelope. The fixed-shape `data` array is decoded directly into public ``Conversation`` values for external callers. +/// +struct ConversationsResponse: Decodable { + struct OCS: Decodable { + struct Meta: Decodable { + let status: String + let statuscode: Int + let message: String? + } + + let meta: Meta + let data: [Conversation] + } + + let ocs: OCS +} diff --git a/Sources/Rainmaker/Server.swift b/Sources/Rainmaker/Server.swift index a71cb48..e26b345 100644 --- a/Sources/Rainmaker/Server.swift +++ b/Sources/Rainmaker/Server.swift @@ -968,6 +968,72 @@ extension Server: Serving { return envelope.ocs.data } + public func conversations() async throws -> [Conversation] { + try requireCredentials() + logger.debug("Fetching Talk conversations...") + + let request = try makeOCSRequest(for: "apps/spreed/api/v4/room", method: .get) + let (data, urlResponse) = try await session.data(for: request) + + guard let response = urlResponse as? HTTPURLResponse else { + throw RainmakerError.responseDecodingFailed(reason: "Failed to cast URLResponse to HTTPURLResponse.") + } + + // The endpoint only exists while the Talk app is installed and enabled, so its absence surfaces as a not found error. + if response.status == .notFound { + throw RainmakerError.notFound + } + + guard response.status == .ok else { + throw RainmakerError.unexpectedStatus(code: response.statusCode) + } + + let envelope = try jsonDecoder.decode(ConversationsResponse.self, from: data) + + guard envelope.ocs.meta.status == "ok" else { + throw RainmakerError.responseDecodingFailed(reason: "OCS request failed (\(envelope.ocs.meta.statuscode)): \(envelope.ocs.meta.message ?? "No message.")") + } + + return envelope.ocs.data + } + + public func conversationAvatar(_ token: String, darkTheme: Bool = false) async throws -> ConversationAvatar { + try requireCredentials() + logger.debug("Fetching the avatar of Talk conversation \(token)...") + + // The dark variant has a sub-route of its own, which the query parameter the endpoint also accepts is deliberately not used for: a path tells the two responses apart wherever requests are keyed by their URL, a query does not. + let path = darkTheme ? "apps/spreed/api/v1/room/\(token)/avatar/dark" : "apps/spreed/api/v1/room/\(token)/avatar" + var request = try makeOCSRequest(for: path, method: .get) + + // Talk caches these responses for a day. The caller decides when to refresh an image, so do not reuse an older response from URLSession's cache. + request.cachePolicy = .reloadIgnoringLocalCacheData + + // The response is an image rather than an OCS payload, so the JSON every other request announces would be a lie. The server answers its error envelope as XML in return, which is why nothing below parses a body. + request.setValue("*/*", forHTTPHeaderField: "Accept") + + let (data, urlResponse) = try await session.data(for: request) + + guard let response = urlResponse as? HTTPURLResponse else { + throw RainmakerError.responseDecodingFailed(reason: "Failed to cast URLResponse to HTTPURLResponse.") + } + + // The endpoint answers the same way for a conversation which does not exist, one this account cannot reach, and a server without the Talk app, so all of those surface as a not found error. + if response.status == .notFound { + throw RainmakerError.notFound + } + + guard response.status == .ok else { + throw RainmakerError.unexpectedStatus(code: response.statusCode) + } + + // Without a stated type the bytes cannot be turned into an image, and guessing between a bitmap and the SVG documents this endpoint commonly serves would be worse than reporting the gap. + guard let contentType = response.value(forHTTPHeaderField: "Content-Type") else { + throw RainmakerError.responseDecodingFailed(reason: "The server did not state the type of the conversation avatar it sent.") + } + + return ConversationAvatar(data: data, contentType: contentType) + } + public func collectives() async throws -> [Collective] { try requireCredentials() logger.debug("Fetching collectives...") diff --git a/Sources/Rainmaker/Serving.swift b/Sources/Rainmaker/Serving.swift index abf9e3a..7e616fe 100644 --- a/Sources/Rainmaker/Serving.swift +++ b/Sources/Rainmaker/Serving.swift @@ -337,6 +337,56 @@ protocol Serving: Sendable { /// func notifications() async throws -> [NotificationItem] + /// + /// List the Nextcloud Talk conversations the authenticated user takes part in. + /// + /// These are provided by the server's Talk app which, like the notes app, is not part of a Nextcloud installation and has to be installed separately. Whether it is available can be checked in advance via the ``Talk`` capability, e.g. `try await capabilities().contains(Talk.self)`. When the app is unavailable the underlying endpoint does not exist and this call throws ``RainmakerError/notFound``. + /// + /// Every conversation the user takes part in is returned, including the ones the server maintains on its own: the Talk app's own release notes as a ``ConversationType/changelog`` conversation and the account's ``ConversationType/noteToSelf``. Downstream projects can derive whether there are any conversations and how many from the returned array via `isEmpty` and `count`. + /// + /// The image of a conversation is retrieved separately through ``conversationAvatar(_:darkTheme:)``. + /// + /// Credentials are required: conversations are user-scoped and the underlying OCS endpoint rejects unauthenticated requests. + /// + /// - Returns: The conversations in the order returned by the server, which the server does not sort deliberately. A client presenting a conversation list is expected to sort it itself, e.g. by ``Conversation/lastActivity`` descending. + /// + /// - Throws: + /// - ``RainmakerError/credentialsRequired`` when no credentials are set. + /// - ``RainmakerError/notFound`` when the Talk app is not available on the server. + /// - ``RainmakerError/unexpectedStatus(code:)`` for any other non-success response. + /// - Any other error that might occur during retrieval. + /// + func conversations() async throws -> [Conversation] + + /// + /// Retrieve the image of a single Nextcloud Talk conversation. + /// + /// The server resolves which image a conversation has, so this returns whatever it decided on and a client renders it as it comes: a picture a moderator uploaded, an emoji picked in the web interface, the other person's avatar in a ``ConversationType/oneToOne`` conversation, or an icon generated from the kind of conversation it is. The generated icons and the emoji avatars are SVG documents rather than bitmaps, which makes ``ConversationAvatar/contentType`` the field to look at before turning the bytes into an image. + /// + /// Each call bypasses the local HTTP cache, because the server permits caching these responses for a day even when the image changes sooner. + /// Cache the returned image between displays and refresh it when ``Conversation/avatarVersion`` changes or a bounded cache lifetime expires, for example after one day. + /// An unchanged version is not sufficient to keep an image indefinitely, and for a ``ConversationType/oneToOne`` conversation it says nothing whatsoever: the server derives that marker from the path of a generic icon, so it is identical for every such conversation and never moves when the other person changes their profile picture. + /// Cache entries must therefore distinguish the server, account, conversation token and appearance rather than the version alone. + /// + /// Note that this is served by version 1 of the Talk API while ``conversations()`` is served by version 4. The two are versioned independently. + /// + /// Credentials are required, and the same availability considerations as for ``conversations()`` apply. + /// + /// - Parameters: + /// - token: The ``Conversation/token`` of the conversation to retrieve the image of. + /// - darkTheme: Whether to retrieve the variant meant for a dark appearance. The implementation on ``Server`` defaults this to `false`. + /// + /// - Returns: The image bytes together with their MIME type. + /// + /// - Throws: + /// - ``RainmakerError/credentialsRequired`` when no credentials are set. + /// - ``RainmakerError/notFound`` when no such conversation exists, it is not accessible to the authenticated user, or the Talk app is not available on the server. The server answers `404` in all of those cases, so they are deliberately not told apart. + /// - ``RainmakerError/unexpectedStatus(code:)`` for any other non-success response. + /// - ``RainmakerError/responseDecodingFailed(reason:)`` when the server does not state the type of the image it sent. + /// - Any other error that might occur during retrieval. + /// + func conversationAvatar(_ token: String, darkTheme: Bool) async throws -> ConversationAvatar + /// /// List the collectives the authenticated user is a member of. /// diff --git a/Sources/RainmakerCLI/Commands/Conversations.swift b/Sources/RainmakerCLI/Commands/Conversations.swift new file mode 100644 index 0000000..fe1f713 --- /dev/null +++ b/Sources/RainmakerCLI/Commands/Conversations.swift @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import ArgumentParser +import Foundation +import Rainmaker + +/// +/// Talk command grouping the conversation listing and the image retrieval of a single conversation. +/// +/// This is a group rather than two top-level commands because both read the same app through the same credentials, mirroring how ``Collectives`` groups its listing and its pages. It is named after the library methods it calls rather than after the app providing them, matching every other subcommand, and calling it `talk` would additionally shadow the library's `Talk` capability inside this module. +/// +struct Conversations: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "List the Talk conversations of the authenticated user and retrieve their images. Requires authentication and the server's Talk app.", + subcommands: [List.self, Avatar.self], + defaultSubcommand: List.self + ) + + /// + /// Conversation listing subcommand. + /// + struct List: AsyncParsableCommand { + static let configuration = CommandConfiguration(abstract: "List the Talk conversations the authenticated user takes part in.") + + @OptionGroup + var authenticatedArguments: AuthenticatedArguments + + @OptionGroup + var formatArguments: FormatArguments + + @OptionGroup + var unauthenticatedArguments: UnauthenticatedArguments + + func run() async throws { + guard let address = URL(string: unauthenticatedArguments.hostValue) else { + throw RainmakerCommandError.invalidAddress + } + + let server = Server(address: address, password: authenticatedArguments.passwordValue, user: authenticatedArguments.userValue) + let conversations = try await server.conversations() + + switch formatArguments.outputFormat { + case .json: + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes] + + let data = try encoder.encode(conversations) + + guard let json = String(data: data, encoding: .utf8) else { + throw RainmakerCommandError.encodingError + } + + print(json) + case .plain: + for conversation in conversations { + // The token is what every further Talk request addresses a conversation by, so surface it next to the name. + print("\(conversation.token)\t\(conversation.displayName)") + } + } + } + } + + /// + /// Image retrieval subcommand of a single conversation. + /// + /// This subcommand deliberately has no ``FormatArguments``: the payload is an image, and the only two things worth doing with it on a command line are writing it somewhere and reporting what it is. Rendering it as JSON would mean base64 on standard output, which serves nobody. + /// + struct Avatar: AsyncParsableCommand { + static let configuration = CommandConfiguration(abstract: "Retrieve the image of a Talk conversation.") + + @OptionGroup + var authenticatedArguments: AuthenticatedArguments + + @OptionGroup + var unauthenticatedArguments: UnauthenticatedArguments + + @Option(help: "The token of the conversation to retrieve the image of, as shown by `conversations list`.") + var token: String + + @Option(help: "Local file to write the image to. Without it, only the type and size of the image are reported.") + var output: String? + + @Flag(help: "Whether to retrieve the variant meant for a dark appearance.") + var darkTheme: Bool = false + + func run() async throws { + guard let address = URL(string: unauthenticatedArguments.hostValue) else { + throw RainmakerCommandError.invalidAddress + } + + let server = Server(address: address, password: authenticatedArguments.passwordValue, user: authenticatedArguments.userValue) + let avatar = try await server.conversationAvatar(token, darkTheme: darkTheme) + + guard let output else { + print("\(avatar.contentType)\t\(avatar.data.count)") + return + } + + // `URL(fileURLWithPath:)` does not expand `~`; do it ourselves so paths like `~/Desktop/avatar.svg` resolve to the user's home directory instead of a literal `~` folder. + let expandedOutput = (output as NSString).expandingTildeInPath + try avatar.data.write(to: URL(fileURLWithPath: expandedOutput)) + + print("\(avatar.contentType)\t\(expandedOutput)") + } + } +} diff --git a/Sources/RainmakerCLI/Commands/Rainmaker.swift b/Sources/RainmakerCLI/Commands/Rainmaker.swift index 10f8d5f..1ad5bce 100644 --- a/Sources/RainmakerCLI/Commands/Rainmaker.swift +++ b/Sources/RainmakerCLI/Commands/Rainmaker.swift @@ -10,7 +10,7 @@ import Foundation @main struct Rainmaker: AsyncParsableCommand { static let configuration: CommandConfiguration = { - var subcommands: [any ParsableCommand.Type] = [Activities.self, ActivityFilters.self, Capabilities.self, Collectives.self, CreateDirectory.self, Delete.self, DeleteAppPassword.self, Download.self, Info.self, List.self, Login.self, Move.self, Navigation.self, Notes.self, NotesSettings.self, Notifications.self, Poll.self, Trash.self, Upload.self, Watch.self] + var subcommands: [any ParsableCommand.Type] = [Activities.self, ActivityFilters.self, Capabilities.self, Collectives.self, Conversations.self, CreateDirectory.self, Delete.self, DeleteAppPassword.self, Download.self, Info.self, List.self, Login.self, Move.self, Navigation.self, Notes.self, NotesSettings.self, Notifications.self, Poll.self, Trash.self, Upload.self, Watch.self] // The fixture recorder controls Docker and is therefore available on macOS only. #if os(macOS) diff --git a/Sources/RainmakerCLI/Fixtures/FixtureOrchestrator.swift b/Sources/RainmakerCLI/Fixtures/FixtureOrchestrator.swift index 3226362..db83f0b 100644 --- a/Sources/RainmakerCLI/Fixtures/FixtureOrchestrator.swift +++ b/Sources/RainmakerCLI/Fixtures/FixtureOrchestrator.swift @@ -25,8 +25,11 @@ /// /// Excluded are suites that never touch the network (request-factory and fixture self-tests) and those whose fixtures are hand-authored with synthetic, version-independent values that a live server cannot reproduce: `TrashTests` (asserts fixed trash identifiers and deletion timestamps), `PollTests` (a successful poll needs a completed browser flow, and it targets the canonical address rather than the container), `NotificationsTests` (the queued notifications depend on server state a plain baseline does not produce) , `ActivityTests` (the recorded activities depend on the same, and the end of the stream, the previews and an absent activity app cannot be provoked on a live container) and `CollectivesTests` (the collectives app is not part of a Nextcloud installation, and installing it mounts a folder of its own into the account's files, which both changes the listings `ListingTests` records and makes the forced baseline reset of ``FixtureProvisioner/reset()`` treat that mount as an orphan to delete). Those are carried forward across versions instead. /// + /// `ConversationsTests` records although the Talk app it needs is not part of a Nextcloud installation either, because Talk keeps its conversations outside the account's files and mounts nothing into them, so neither `ListingTests` nor the baseline reset notices it. See ``installedApps``. + /// static let recordableSuites: Set = [ "CapabilitiesTests", + "ConversationsTests", "CreateDirectoryTests", "DeleteTests", "DownloadTests", @@ -40,15 +43,27 @@ ] /// - /// The app installed on every deployed container, because `NotesTests` cannot record anything without it. + /// The identifier of the notes app, which `NotesTests` cannot record anything without. + /// + /// It is named on its own rather than only as part of ``installedApps`` because what follows its installation is a lookup specific to it, namely ``resolveNotesFolder(on:)``. /// - /// The notes app is not part of a Nextcloud installation, so without this the endpoints behind ``Server/notes()`` would not exist and the suite would capture nothing but a not found response. Recording therefore depends on the Nextcloud app store being reachable, which serves the newest release compatible with the server version deployed. + static let notesApp = "notes" + /// - /// It is declared through ``NextcloudConfiguration/enabledApps``, so ``NextcloudContainerManager/deploy(configuration:)`` installs and enables it before returning, under the app store allowance of ``NextcloudContainerManager/defaultAppInstallationTimeout``. Everything after deployment can therefore assume the app is already answering. + /// The identifier of the Talk app, which `ConversationsTests` cannot record anything without. /// - /// Naming a single app rather than a list is deliberate: what follows the installation is a lookup specific to this app, so a list would only pretend to generalize. + /// Talk is the app's presented name while `spreed` is the identifier it has always had internally, which is also the key it advertises its ``Talk`` capability under. /// - static let installedApp = "notes" + static let talkApp = "spreed" + + /// + /// The apps installed on every deployed container, because the suites covering them cannot record anything without them. + /// + /// Neither the notes app nor the Talk app is part of a Nextcloud installation, so without this the endpoints behind ``Server/notes()`` and ``Server/conversations()`` would not exist and those suites would capture nothing but a not found response. Recording therefore depends on the Nextcloud app store being reachable, which serves the newest release compatible with the server version deployed. That also means the fixtures of those two suites track a release of their app rather than a release of the server, which is why their assertions rest on state ``FixtureProvisioner`` seeds instead of on whatever the app creates by itself. + /// + /// They are declared through ``NextcloudConfiguration/enabledApps``, so ``NextcloudContainerManager/deploy(configuration:)`` installs and enables them before returning, under the app store allowance of ``NextcloudContainerManager/defaultAppInstallationTimeout``. Everything after deployment can therefore assume both apps are already answering, which each of them is nevertheless probed for. + /// + static let installedApps = [notesApp, talkApp] /// /// How long to keep retrying a readiness probe before giving up. @@ -62,7 +77,7 @@ /// static let disabledApps = [ "bruteforcesettings", - // Disabling the dashboard makes "files" the user's default app (the default `defaultapp` order is "dashboard,files"), which `NavigationTests` asserts on. + // Disabling the dashboard takes it out of the running for the user's default app, which the default `defaultapp` order of "dashboard,files" would otherwise give it. Which app ends up default is asserted by `NavigationTests`; since Talk joined ``installedApps`` that is Talk, which registers itself ahead of everything else with a negative navigation order. "dashboard", "firstrunwizard", "nextcloud_announcements", @@ -122,7 +137,7 @@ /// private func record(version: String, tests: [String]) async throws { log("Deploying nextcloud:\(version)…") - let container = try await NextcloudContainerManager.deploy(configuration: NextcloudConfiguration(tag: version, disabledApps: Self.disabledApps, enabledApps: [Self.installedApp])) + let container = try await NextcloudContainerManager.deploy(configuration: NextcloudConfiguration(tag: version, disabledApps: Self.disabledApps, enabledApps: Self.installedApps)) log("Container ready at http://localhost:\(container.port) (id \(container.id.prefix(12))).") do { @@ -132,8 +147,11 @@ // Provisioning is the first thing to touch WebDAV, so this is where the wait for it belongs. _ = try await waitUntil("the account's WebDAV endpoint") { try await server.info("/") } + // The Talk app is probed the same way, because its own endpoint is what the conversation fixtures depend on and it answers at its own pace. + _ = try await waitUntil("the \(Self.talkApp) app") { try await server.conversations() } + let notesFolder = try await resolveNotesFolder(on: server) - log("The \(Self.installedApp) app stores notes in \"\(notesFolder)\".") + log("The \(Self.notesApp) app stores notes in \"\(notesFolder)\".") let baselineDirectory = FileManager.default.temporaryDirectory.appendingPathComponent("rainmaker-baseline-\(version)") let provisioner = FixtureProvisioner(server: server, baselineDirectory: baselineDirectory, notesFolder: notesFolder) @@ -250,7 +268,7 @@ /// It is retried because ``NextcloudContainerManager/deploy(configuration:)`` returning means the app is installed and enabled, not that the running web server serves it yet. The first request after a deployment has been observed answering with a not found error for a moment, so what is waited out here is that window rather than the installation, which the deployment already covered. /// private func resolveNotesFolder(on server: Server) async throws -> String { - try await waitUntil("the \(Self.installedApp) app") { try await server.notesSettings().notesPath } + try await waitUntil("the \(Self.notesApp) app") { try await server.notesSettings().notesPath } } /// diff --git a/Sources/RainmakerCLI/Fixtures/FixtureProvisioner.swift b/Sources/RainmakerCLI/Fixtures/FixtureProvisioner.swift index e3f8a03..653c474 100644 --- a/Sources/RainmakerCLI/Fixtures/FixtureProvisioner.swift +++ b/Sources/RainmakerCLI/Fixtures/FixtureProvisioner.swift @@ -45,6 +45,13 @@ (name: "Recipes/Pancakes.md", contents: "# Pancakes\n", modification: Date(timeIntervalSince1970: 1_600_000_000)), ] + /// + /// The name of the Talk conversation seeded for `ConversationsTests`, which is what makes it findable without relying on a token or an identifier. + /// + /// A conversation with a known name is needed because a plain Talk installation only creates conversations of its own, whose names are localized to the account's language and whose unread counts follow the release of the app rather than of the server. The same literal appears in the test suite, which cannot import this module. + /// + static let seededConversation = "Rainmaker" + /// /// Generate the baseline tree on disk and upload it to the server. /// @@ -79,6 +86,8 @@ } else if testID.contains("UploadTests/directory") { // The fixture creates "/Documents" fresh (MKCOL → 201), so it must be absent beforehand. try? await server.delete("/Documents") + } else if testID.contains("ConversationsTests/") { + try await seedConversation() } else if testID.contains("NotesTests/fetchNone") { // The fixture records an account without a single note, so the seeded notes folder has to be gone beforehand. The baseline is restored before every following test, which brings it back. try? await server.delete("/\(notesFolder)") @@ -93,6 +102,33 @@ // MARK: - Private + /// + /// Create the Talk conversation named ``seededConversation`` unless it already exists. + /// + /// Conversations live outside the account's files, so ``reset()`` neither removes nor restores them and the one created here survives for the rest of the container's life. That is why this is idempotent and why every test of the suite asks for it rather than only the first: the recorder runs them in whichever order `swift test list` reports, so no test can rely on another having run before it. + /// + /// Talk has no WebDAV surface to provision through, so the request is built with ``Server/makeOCSRequest(for:method:queryItems:)`` and performed on a session of its own. That is the sanctioned route for reaching an endpoint the library itself does not cover. + /// + private func seedConversation() async throws { + let existing = try await server.conversations() + + guard existing.contains(where: { $0.displayName == Self.seededConversation }) == false else { + return + } + + var request = try server.makeOCSRequest(for: "apps/spreed/api/v4/room", method: .post) + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + // A room type of 2 is a group conversation, which is the kind a client creates and therefore the kind worth recording. + request.httpBody = try JSONSerialization.data(withJSONObject: ["roomType": 2, "roomName": Self.seededConversation]) + + let (_, urlResponse) = try await URLSession(configuration: .ephemeral).data(for: request) + + guard let response = urlResponse as? HTTPURLResponse, response.statusCode == 200 || response.statusCode == 201 else { + throw FixtureRecordingError.conversationSeedingFailed + } + } + /// /// Generate the controlled baseline tree on the local file system. /// diff --git a/Sources/RainmakerCLI/Fixtures/FixtureRecordingError.swift b/Sources/RainmakerCLI/Fixtures/FixtureRecordingError.swift index 87183fe..e1b09e5 100644 --- a/Sources/RainmakerCLI/Fixtures/FixtureRecordingError.swift +++ b/Sources/RainmakerCLI/Fixtures/FixtureRecordingError.swift @@ -23,12 +23,19 @@ /// case serverNotReady(subject: String, reason: String) + /// + /// The Talk conversation the conversation fixtures are recorded against could not be created. + /// + case conversationSeedingFailed + var description: String { switch self { case .verificationFailed: "The replay-verify pass failed: the recorded fixtures do not reproduce passing tests without a server. Inspect the test output above and, for any failing test, add its precondition in FixtureProvisioner.applyPrecondition(forTest:)." case let .serverNotReady(subject: subject, reason: reason): "Gave up waiting for \(subject): \(reason)." + case .conversationSeedingFailed: + "The Talk conversation the conversation fixtures are recorded against could not be created. Check that the Talk app is installed and enabled for the account being recorded." case let .appInstallationFailed(app): "The \"\(app)\" app could not be installed into the container. It is fetched from the Nextcloud app store, so check that the store is reachable and that it offers a release compatible with the server version being recorded." } diff --git a/Tests/RainmakerTests/CapabilitiesTests.swift b/Tests/RainmakerTests/CapabilitiesTests.swift index 94eb5b9..41c398d 100644 --- a/Tests/RainmakerTests/CapabilitiesTests.swift +++ b/Tests/RainmakerTests/CapabilitiesTests.swift @@ -66,6 +66,15 @@ import Testing #expect(notes.notesPath?.isEmpty == false) #expect(capabilities.contains(Notes.self)) + // Talk is likewise installed on the recording containers on demand. Its features are named flags rather than an API version, because it versions its endpoints in their paths instead. + let talk = try #require(try capabilities.get(Talk.self)) + #expect(talk.version?.isEmpty == false) + #expect(talk.features?.isEmpty == false) + #expect(talk.localFeatures?.isEmpty == false) + #expect(talk.supports("conversation-v4")) + #expect(talk.supports("no-such-talk-feature") == false) + #expect(capabilities.contains(Talk.self)) + #expect(capabilities.version.string == serverVersion.rawValue) #expect(capabilities.version.major > 0) } @@ -86,6 +95,11 @@ import Testing #expect(try capabilities.get(Notes.self) == nil) #expect(capabilities.contains(Notes.self) == false) + // Talk is the exception: unlike every other app covered here it advertises itself to anonymous clients as well, so its presence says the app is installed rather than that this client may use it. + let talk = try #require(try capabilities.get(Talk.self)) + #expect(talk.supports("conversation-v4")) + #expect(capabilities.contains(Talk.self)) + #expect(capabilities.version.string == serverVersion.rawValue) } } diff --git a/Tests/RainmakerTests/ConversationsRequestTests.swift b/Tests/RainmakerTests/ConversationsRequestTests.swift new file mode 100644 index 0000000..fee5a00 --- /dev/null +++ b/Tests/RainmakerTests/ConversationsRequestTests.swift @@ -0,0 +1,304 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import Foundation +@testable import Rainmaker +import Testing + +/// +/// About how ``Server/conversations()`` and ``Server/conversationAvatar(_:darkTheme:)`` build their requests and how they decode what the server answers with. +/// +/// These tests deliberately do not use the fixture tree: it is keyed by HTTP method and URL path only, so a replayed test cannot prove what a request carried beyond its path, and a live container cannot be made to produce the responses which matter most here. A capturing ``MockRequesting`` is used instead, which serves the responses of a server without the Talk app, of a conversation which does not exist, of a failed OCS envelope, of a conversation kind this library does not know, and of an image the server did not state the type of. +/// +/// The happy paths against a real server are covered by ``ConversationsTests``. +/// +@Suite("Conversation Requests") struct ConversationsRequestTests { + let serverAddress = URL(string: "http://localhost/")! + + /// + /// The response of an account without a single conversation, enough for a call to succeed so the captured request can be inspected. + /// + let noConversations = #"{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[]}}"# + + /// + /// The bytes of an image, standing in for whatever the avatar endpoint serves. + /// + let imageBytes = #""# + + /// + /// Build a server whose session is the given mock. + /// + private func makeServer(session: MockRequesting) -> Server { + Server(address: serverAddress, password: "admin", user: "admin", session: session, userAgent: "RainmakerTests") + } + + /// + /// Build a server answering every request with the given body, status and header fields. + /// + private func makeServer(body: String, statusCode: Int = 200, headerFields: [String: String]? = nil) -> Server { + makeServer(session: MockRequesting(string: body, statusCode: statusCode, headerFields: headerFields)) + } + + /// + /// Build a server answering every request with an image of the given media type. + /// + private func makeImageServer(contentType: String = "image/svg+xml") -> Server { + makeServer(body: imageBytes, headerFields: ["Content-Type": contentType]) + } + + /// + /// Return the path and the query items of the single request the given mock captured, keyed by name. + /// + private func captured(from session: MockRequesting) throws -> (path: String, query: [String: String]) { + let request = try #require(session.requests.first) + let url = try #require(request.url) + let components = try #require(URLComponents(url: url, resolvingAgainstBaseURL: false)) + + let query = (components.queryItems ?? []).reduce(into: [String: String]()) { result, item in + result[item.name] = item.value + } + + return (components.path, query) + } + + // MARK: - Request Construction + + @Test("Conversation Listing Targets The Room Endpoint") + func conversationsPath() async throws { + let session = MockRequesting(string: noConversations) + _ = try await makeServer(session: session).conversations() + + let (path, query) = try captured(from: session) + + // The server's API calls a conversation a room, and the listing is served by version 4 of it. + #expect(path == "/ocs/v2.php/apps/spreed/api/v4/room") + + // Every query parameter this endpoint takes has a server side default, so none is sent at all. + #expect(query.isEmpty) + } + + @Test("Avatar Retrieval Carries The Token In Its Path") + func avatarPath() async throws { + let session = MockRequesting(string: imageBytes, headerFields: ["Content-Type": "image/svg+xml"]) + _ = try await makeServer(session: session).conversationAvatar("oypsiy6j") + + let (path, query) = try captured(from: session) + + // The avatar is served by version 1 of the API while the listing is served by version 4. The two are versioned independently, which makes this the easiest thing to get wrong. + #expect(path == "/ocs/v2.php/apps/spreed/api/v1/room/oypsiy6j/avatar") + #expect(query.isEmpty) + } + + @Test("Dark Avatar Retrieval Targets The Dedicated Sub Route") + func darkAvatarPath() async throws { + let session = MockRequesting(string: imageBytes, headerFields: ["Content-Type": "image/svg+xml"]) + _ = try await makeServer(session: session).conversationAvatar("oypsiy6j", darkTheme: true) + + let (path, query) = try captured(from: session) + + // The endpoint also accepts a query parameter for this, which is deliberately not used: the fixture tree is keyed by path and drops the query, so both appearances would otherwise collapse into one directory. + #expect(path == "/ocs/v2.php/apps/spreed/api/v1/room/oypsiy6j/avatar/dark") + #expect(query.isEmpty) + } + + @Test("Conversation Listing Is An Authenticated OCS Request") + func requestHeaders() async throws { + let session = MockRequesting(string: noConversations) + _ = try await makeServer(session: session).conversations() + + let request = try #require(session.requests.first) + let headers = request.allHTTPHeaderFields + + #expect(request.httpMethod == "GET") + #expect(headers?["Accept"] == "application/json") + #expect(headers?["User-Agent"] == "RainmakerTests") + #expect(headers?["Authorization"] == "Basic \(Data("admin:admin".utf8).base64EncodedString())") + + // The Talk API is reachable through OCS, so the header announcing that has to be sent. + #expect(headers?["OCS-APIRequest"] == "true") + } + + @Test("Avatar Retrieval Announces That It Expects No Payload") + func avatarRequestHeaders() async throws { + let session = MockRequesting(string: imageBytes, headerFields: ["Content-Type": "image/svg+xml"]) + _ = try await makeServer(session: session).conversationAvatar("oypsiy6j") + + let request = try #require(session.requests.first) + let headers = request.allHTTPHeaderFields + + // The response is an image rather than an OCS payload, so announcing JSON as every other request does would be a lie. This is also what makes the recorded fixture a binary one. + #expect(headers?["Accept"] == "*/*") + #expect(headers?["Authorization"] == "Basic \(Data("admin:admin".utf8).base64EncodedString())") + } + + @Test("Avatar Retrieval Requests A Fresh Image", arguments: [false, true]) + func avatarRequestFreshness(darkTheme: Bool) async throws { + let session = MockRequesting(string: imageBytes, headerFields: ["Content-Type": "image/svg+xml"]) + _ = try await makeServer(session: session).conversationAvatar("oypsiy6j", darkTheme: darkTheme) + + let request = try #require(session.requests.first) + + // The server permits caching an avatar for a day and marks it immutable, and a `URLSession` keeps an in memory cache even when it is configured as ephemeral. Retrieving an avatar is an explicit act by the caller, so it must reach the server rather than be answered from that cache. Both appearances are checked because each is a route of its own. + #expect(request.cachePolicy == .reloadIgnoringLocalCacheData) + } + + // MARK: - Credentials + + @Test("Conversation Listing Requires Credentials") + func requireCredentials() async throws { + let session = MockRequesting(string: noConversations) + let server = Server(address: serverAddress, password: nil, user: nil, session: session, userAgent: "RainmakerTests") + + await #expect(throws: RainmakerError.credentialsRequired) { + _ = try await server.conversations() + } + + // Credentials are required, so the call fails before any network request is made. + #expect(session.requests.isEmpty) + } + + @Test("Avatar Retrieval Requires Credentials") + func requireAvatarCredentials() async throws { + let session = MockRequesting(string: imageBytes) + let server = Server(address: serverAddress, password: nil, user: nil, session: session, userAgent: "RainmakerTests") + + await #expect(throws: RainmakerError.credentialsRequired) { + _ = try await server.conversationAvatar("oypsiy6j") + } + + #expect(session.requests.isEmpty) + } + + // MARK: - Availability + + @Test("Unavailable App Is Not Found") + func unavailableApp() async throws { + // Without the Talk app installed and enabled the OCS route does not exist, which the server reports with an envelope of its own rather than with the app's. + let body = #"{"ocs":{"meta":{"status":"failure","statuscode":998,"message":"Invalid query, please check the syntax."},"data":[]}}"# + let server = makeServer(body: body, statusCode: 404) + + await #expect(throws: RainmakerError.notFound) { + _ = try await server.conversations() + } + } + + @Test("Unknown Conversation Is Not Found") + func unknownConversation() async throws { + // A conversation which does not exist, or which this account cannot reach, is answered with the very same status as an absent app, which is why the two are deliberately not told apart. + let body = #"failure404"# + let server = makeServer(body: body, statusCode: 404) + + await #expect(throws: RainmakerError.notFound) { + _ = try await server.conversationAvatar("zzzzzzzz") + } + } + + @Test("Rejected Credentials Are Reported As A Status") + func rejectedCredentials() async throws { + // A revoked app password is not the same as an absent app, so it must not collapse into a not found error. + let body = #"{"ocs":{"meta":{"status":"failure","statuscode":997,"message":"Current user is not logged in"},"data":[]}}"# + let server = makeServer(body: body, statusCode: 401) + + await #expect(throws: RainmakerError.unexpectedStatus(code: 401)) { + _ = try await server.conversations() + } + } + + @Test("Failed OCS Envelope Is A Decoding Failure") + func failedEnvelope() async throws { + // A success status carrying a failed envelope is what OCS answers with in some error cases, so the envelope's own status has to be checked rather than trusted. + let body = #"{"ocs":{"meta":{"status":"failure","statuscode":500,"message":"Something went wrong"},"data":[]}}"# + let server = makeServer(body: body) + + await #expect { + _ = try await server.conversations() + } throws: { error in + guard case let RainmakerError.responseDecodingFailed(reason: reason) = error else { + return false + } + + return reason.contains("500") && reason.contains("Something went wrong") + } + } + + @Test("Malformed Success Response Is A Decoding Failure") + func malformedResponse() async throws { + let server = makeServer(body: #"{"unexpected":true}"#) + + // A body which is not an OCS envelope must surface as an error rather than as an empty listing. + await #expect(throws: (any Error).self) { + _ = try await server.conversations() + } + } + + // MARK: - Decoding + + @Test("Conversations Decode From What A Live Server Sends") + func decodesConversation() async throws { + // Verbatim what a live server sends, trimmed to a single conversation. Everything this library does not model, from the permission bit fields to the whole nested last message, has to be ignored rather than break the listing. + let body = #"{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":2,"token":"by9jp6mr","type":6,"name":"Note to self","displayName":"Note to self","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"canStartCall":false,"lastActivity":1700000000,"lastReadMessage":5,"unreadMessages":3,"unreadMention":true,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":510,"attendeePin":"","description":"A place for your private notes","lastCommonReadMessage":5,"listable":0,"messageExpiration":0,"avatarVersion":"Rw2KnG5yXX5AdMzz","isCustomAvatar":true,"isArchived":false,"tagIds":[],"attributes":0,"lastMessage":{"id":5,"token":"by9jp6mr","actorDisplayName":"","timestamp":1700000000,"message":"System created the conversation","systemMessage":"conversation_created"}}]}}"# + let conversation = try #require(try await makeServer(body: body).conversations().first) + + #expect(conversation.id == 2) + #expect(conversation.token == "by9jp6mr") + #expect(conversation.displayName == "Note to self") + #expect(conversation.avatarVersion == "Rw2KnG5yXX5AdMzz") + #expect(conversation.unreadMessages == 3) + #expect(conversation.unreadMention) + + // The raw `6` the server sends is meaningless on its own, which is the whole reason the kind is modelled as a case. + #expect(conversation.type == .noteToSelf) + + // The server sends whole seconds since the Unix epoch, which the model converts itself rather than leaning on a decoder's date strategy. + #expect(conversation.lastActivity == Date(timeIntervalSince1970: 1_700_000_000)) + + #expect(conversation.description == "Note to self") + #expect(conversation.debugDescription == "#2 (by9jp6mr): Note to self") + } + + @Test("Unknown Conversation Kind Does Not Fail The Listing") + func decodesUnknownType() async throws { + // A kind introduced by a future release of the Talk app must not take the whole listing down with it, which a strict enum would. + let body = #"{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":9,"token":"aaaabbbb","type":99,"displayName":"From the future","lastActivity":1700000000,"unreadMessages":0,"unreadMention":false,"avatarVersion":"00000000"}]}}"# + let conversations = try await makeServer(body: body).conversations() + + #expect(conversations.count == 1) + #expect(conversations.first?.type == .other(99)) + #expect(conversations.first?.token == "aaaabbbb") + } + + @Test("Conversation Without Activity Decodes As The Epoch") + func decodesAbsentActivity() async throws { + // A conversation nothing has happened in yet is reported as a zero timestamp rather than as an absent field, which still sorts as the oldest. + let body = #"{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":9,"token":"aaaabbbb","type":2,"displayName":"Fresh","lastActivity":0,"unreadMessages":0,"unreadMention":false,"avatarVersion":"00000000"}]}}"# + let conversation = try #require(try await makeServer(body: body).conversations().first) + + #expect(conversation.lastActivity == Date(timeIntervalSince1970: 0)) + } + + @Test("Avatar Carries The Bytes And The Type The Server Sent") + func decodesAvatar() async throws { + let avatar = try await makeImageServer().conversationAvatar("oypsiy6j") + + #expect(avatar.data == Data(imageBytes.utf8)) + + // Every generated icon and every emoji avatar is an SVG document rather than a bitmap, so the type is what a client has to look at before rendering. + #expect(avatar.contentType == "image/svg+xml") + } + + @Test("Avatar Of An Unstated Type Is A Decoding Failure") + func avatarWithoutContentType() async throws { + let server = makeServer(body: imageBytes) + + // Guessing between a bitmap and an SVG document would be worse than reporting that the server did not say. + await #expect { + _ = try await server.conversationAvatar("oypsiy6j") + } throws: { error in + guard case let RainmakerError.responseDecodingFailed(reason: reason) = error else { + return false + } + + return reason.contains("type") + } + } +} diff --git a/Tests/RainmakerTests/ConversationsTests.swift b/Tests/RainmakerTests/ConversationsTests.swift new file mode 100644 index 0000000..03ebcbb --- /dev/null +++ b/Tests/RainmakerTests/ConversationsTests.swift @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2026 Iva Horn +// SPDX-License-Identifier: MIT + +import Foundation +@testable import Rainmaker +import RainmakerTestServerTags +import Testing + +/// +/// About listing the Talk conversations of the authenticated user and retrieving their images. +/// +/// The fixtures backing this suite are recorded against a live container with the Talk app installed, which `FixtureOrchestrator` declares alongside the notes app. Talk keeps its conversations outside the account's files and mounts nothing into them, which is what makes recording possible here while ``CollectivesTests`` has to do without it. +/// +/// The conversation asserted on is the one ``FixtureProvisioner`` seeds, and it is looked up by name rather than by token, identifier or position. That is not a convenience: a plain Talk installation creates conversations of its own whose names are localized to the account's language, whose unread counts follow the release of the app rather than of the server, and whose very existence differs between releases. Nothing about them is worth asserting, so nothing here does. +/// +/// The tokens the fixtures carry are the ones the recorded server assigned, because a token is also part of the path the avatar of a conversation is fetched under and therefore of where its fixture lives. See ``FixtureCanonicalizer``. +/// +/// How a request is built, how an absent app, an unknown conversation and an unknown conversation kind surface, and how the payloads decode is covered by ``ConversationsRequestTests`` instead, which drives a mock rather than the fixture tree. +/// +@Suite("Conversations") struct ConversationsTests: ServerTesting { + /// + /// The name of the conversation ``FixtureProvisioner`` seeds, which is what makes it findable without relying on a token. + /// + let seededConversation = "Rainmaker" + + @Test("Require Credentials", arguments: ServerVersion.allCases) + func requireCredentials(_ serverVersion: ServerVersion) async throws { + let server = try makeServer(user: nil, password: nil, serverVersion: serverVersion) + + // Credentials are required, so the call fails before any network request is made. + await #expect(throws: RainmakerError.credentialsRequired) { + _ = try await server.conversations() + } + } + + @Test("Fetch", arguments: ServerVersion.allCases) + func fetch(_ serverVersion: ServerVersion) async throws { + let server = try makeServer(serverVersion: serverVersion) + let conversations = try await server.conversations() + + // Downstream projects rely on the count to learn whether and how many conversations exist. + #expect(conversations.isEmpty == false) + + // Whatever a given release of the Talk app creates by itself, every conversation is addressable and cacheable, which is what a client surfacing them needs. + #expect(conversations.allSatisfy { $0.id > 0 }) + #expect(conversations.allSatisfy { $0.token.isEmpty == false }) + #expect(conversations.allSatisfy { $0.avatarVersion.isEmpty == false }) + + // Every kind a supported server sends has a case of its own, so anything falling through to the unknown one means the enum is behind. + let unknownKinds = conversations.filter { conversation in + if case .other = conversation.type { + return true + } + + return false + } + + #expect(unknownKinds.isEmpty) + + // Looked up by name rather than by position or token: the tokens the server assigns differ per deployment. + let conversation = try #require(conversations.first { $0.displayName == seededConversation }) + + #expect(conversation.type == .group) + #expect(conversation.unreadMessages == 0) + #expect(conversation.unreadMention == false) + #expect(conversation.description == seededConversation) + #expect(conversation.debugDescription == "#\(conversation.id) (\(conversation.token)): \(seededConversation)") + + // Only that the activity decoded at all is asserted, never its value: ``FixtureCanonicalizer`` flattens it to a fixed moment because the recorded one is the time of the recording, and a recording run asserts against the live response rather than against what it persisted. The conversion from the whole seconds the server sends is pinned exactly in ``ConversationsRequestTests`` instead. + #expect(conversation.lastActivity > Date(timeIntervalSince1970: 0)) + + // Every account gets a conversation with itself, which is the one kind besides the seeded group conversation that every supported release creates alike. + #expect(conversations.contains { $0.type == .noteToSelf }) + } + + @Test("Avatar", arguments: ServerVersion.allCases) + func avatar(_ serverVersion: ServerVersion) async throws { + let server = try makeServer(serverVersion: serverVersion) + let conversations = try await server.conversations() + let conversation = try #require(conversations.first { $0.displayName == seededConversation }) + // The variant is named explicitly because ``ServerTesting/makeServer(user:password:serverVersion:testSourceCodeFile:testName:)`` hands out a ``Serving``, and the default lives on the concrete ``Server`` rather than on the protocol. + let avatar = try await server.conversationAvatar(conversation.token, darkTheme: false) + + #expect(avatar.data.isEmpty == false) + + // Which image a conversation has is the server's decision, so only that it is an image at all can be asserted. A conversation nobody set a picture for gets a generated SVG document today, which is not a contract. + #expect(avatar.contentType.hasPrefix("image/")) + } +} diff --git a/Tests/RainmakerTests/FixtureCanonicalizer.swift b/Tests/RainmakerTests/FixtureCanonicalizer.swift index 60a576b..b98242d 100644 --- a/Tests/RainmakerTests/FixtureCanonicalizer.swift +++ b/Tests/RainmakerTests/FixtureCanonicalizer.swift @@ -85,7 +85,9 @@ struct FixtureCanonicalizer { /// /// Binary bodies (`bin`) are returned unchanged. Textual bodies have the live origin rewritten and volatile fields replaced with fixed placeholders. A body which is not valid UTF-8 is returned unchanged. /// - func canonicalizedBody(_ data: Data, pathExtension: String) -> Data { + /// The request URL decides which replacements apply. Beyond ``volatileReplacements``, which every body gets, there are rules whose field names are not unique across this server's APIs, and applying those everywhere rewrites values which happen to share a name with something volatile elsewhere. Scoping them to the API they describe is what keeps the login flow's single-use `token` from being confused with the token addressing a Talk conversation. + /// + func canonicalizedBody(_ data: Data, pathExtension: String, requestURL: URL) -> Data { guard pathExtension != "bin" else { return data } @@ -96,7 +98,19 @@ struct FixtureCanonicalizer { text = text.replacingOccurrences(of: liveAuthority, with: canonicalHost) - for (pattern, replacement) in Self.volatileReplacements { + // `URLComponents` is used rather than the modern `URL` path accessor for the same reason the initializer uses it: the accessor is unavailable on the older platforms the package supports. + let path = URLComponents(url: requestURL, resolvingAgainstBaseURL: false)?.path ?? "" + var replacements = Self.volatileReplacements + + if path.contains("/login/v2") { + replacements += Self.loginReplacements + } + + if path.contains("/apps/spreed/") { + replacements += Self.talkReplacements + } + + for (pattern, replacement) in replacements { text = text.replacingOccurrences(of: pattern, with: replacement, options: .regularExpression) } @@ -112,6 +126,8 @@ struct FixtureCanonicalizer { /// /// The JSON `"etag"` rule covers the notes the notes API reports, whose entity tags are content hashes differing per deployment. It also rewrites the one under `capabilities.files.directEditing` in the capabilities fixtures, the only other JSON fixture carrying that key, which is an entity tag as well and which no test asserts on. /// + /// The `"hello-v2-token-key"` rule covers the public key the Talk app advertises under its capabilities, which is generated per installation. Its name is unique to that one field, which is why it belongs here rather than in a scoped set. + /// /// Two neighbouring fields of a note are deliberately left alone. Its `"modified"` timestamp is reproducible rather than volatile, because ``FixtureProvisioner`` stamps the seeded note files with fixed modification dates which ``Server/upload(_:to:force:)`` preserves, and flattening it would remove the very difference the incremental retrieval tests rely on. Its numeric `"id"` is a database identifier which does differ per deployment, but collapsing every note's id to one placeholder would make them indistinguishable and contradict `Note` being `Identifiable`, so the tests look notes up by title instead. /// private static let volatileReplacements: [(pattern: String, replacement: String)] = [ @@ -120,10 +136,30 @@ struct FixtureCanonicalizer { ("[^<]*", "00000000000000000000000000000000"), ("[^<]*", "0"), ("[^<]*", "/remote.php/dav/comments/files/0"), + ("\"etag\"[ ]*:[ ]*\"[^\"]*\"", "\"etag\": \"00000000000000000000000000000000\""), + ("\"hello-v2-token-key\"[ ]*:[ ]*\"[^\"]*\"", "\"hello-v2-token-key\": \"REDACTED\""), + ] + + /// + /// Replacements applied only to the bodies of the login flow, which is the one API whose `token` is a secret rather than an identifier. + /// + /// These used to be part of ``volatileReplacements`` and applied to every body, which was safe only while the login flow was the sole API sending a field named `token`. Talk sends one too, and its value addresses a conversation in every further request, so the two must not share a rule. + /// + private static let loginReplacements: [(pattern: String, replacement: String)] = [ ("/login/v2/flow/[^\"<\\s]+", "/login/v2/flow/REDACTED"), ("\"token\"[ ]*:[ ]*\"[^\"]*\"", "\"token\": \"REDACTED\""), ("\"appPassword\"[ ]*:[ ]*\"[^\"]*\"", "\"appPassword\": \"REDACTED\""), - ("\"etag\"[ ]*:[ ]*\"[^\"]*\"", "\"etag\": \"00000000000000000000000000000000\""), + ] + + /// + /// Replacements applied only to the bodies of the Talk API, whose conversations carry timestamps and an avatar version differing per deployment. + /// + /// The token of a conversation is deliberately *not* replaced. It is not only asserted on, it is part of the path of every further request about that conversation, so the fixture directory of ``Server/conversationAvatar(_:darkTheme:)`` is named after it. Rewriting it in the listing while the recorded directory keeps the live value would send a replayed lookup to a directory which does not exist. Its churn across regenerations is therefore accepted. + /// + private static let talkReplacements: [(pattern: String, replacement: String)] = [ + ("\"lastActivity\"[ ]*:[ ]*[0-9]+", "\"lastActivity\": 1700000000"), + ("\"timestamp\"[ ]*:[ ]*[0-9]+", "\"timestamp\": 1700000000"), + ("\"avatarVersion\"[ ]*:[ ]*\"[^\"]*\"", "\"avatarVersion\": \"00000000\""), ] /// diff --git a/Tests/RainmakerTests/FixtureCanonicalizerTests.swift b/Tests/RainmakerTests/FixtureCanonicalizerTests.swift index 105f0a3..3d6b5c3 100644 --- a/Tests/RainmakerTests/FixtureCanonicalizerTests.swift +++ b/Tests/RainmakerTests/FixtureCanonicalizerTests.swift @@ -13,8 +13,15 @@ import Testing @Suite("Fixture Canonicalizer") struct FixtureCanonicalizerTests { let canonicalizer = FixtureCanonicalizer(liveServerAddress: URL(string: "http://localhost:54540/")!) - func canonicalize(_ string: String, pathExtension: String) -> String { - let result = canonicalizer.canonicalizedBody(Data(string.utf8), pathExtension: pathExtension) + /// + /// Canonicalize a body as if it had been the response to a request for the given path. + /// + /// The path matters because ``FixtureCanonicalizer`` scopes some of its replacements to the API they describe, so a rule can only be exercised through a request it actually applies to. + /// + func canonicalize(_ string: String, pathExtension: String, path: String = "/remote.php/dav/files/admin/") -> String { + let requestURL = URL(string: "http://localhost:54540\(path)")! + let result = canonicalizer.canonicalizedBody(Data(string.utf8), pathExtension: pathExtension, requestURL: requestURL) + return String(data: result, encoding: .utf8) ?? "" } @@ -46,13 +53,45 @@ import Testing @Test("Redacts login tokens and app passwords") func redactsTokens() { let body = "{\"token\": \"abc123\", \"appPassword\": \"secret\", \"login\": \"http://localhost:54540/login/v2/flow/XYZ789\"}" - let result = canonicalize(body, pathExtension: "json") + let result = canonicalize(body, pathExtension: "json", path: "/index.php/login/v2") #expect(result.contains("\"token\": \"REDACTED\"")) #expect(result.contains("\"appPassword\": \"REDACTED\"")) #expect(result.contains("http://localhost/login/v2/flow/REDACTED")) } + @Test("Leaves login secrets alone outside the login flow") + func leavesLoginSecretsScoped() { + let body = "{\"token\": \"abc123\", \"appPassword\": \"secret\"}" + let result = canonicalize(body, pathExtension: "json") + + // These names are only secrets in the login flow. Redacting them everywhere would rewrite an unrelated field which happens to share a name, which is exactly what the Talk conversation token used to suffer from. + #expect(result.contains("\"token\": \"abc123\"")) + #expect(result.contains("\"appPassword\": \"secret\"")) + } + + @Test("Redacts the volatile conversation fields but keeps the token") + func redactsConversationFields() { + let body = "{\"token\":\"oypsiy6j\",\"lastActivity\":1788879440,\"avatarVersion\":\"b0135871\",\"lastMessage\":{\"timestamp\":1788879440}}" + let result = canonicalize(body, pathExtension: "json", path: "/ocs/v2.php/apps/spreed/api/v4/room") + + #expect(result.contains("\"lastActivity\": 1700000000")) + #expect(result.contains("\"timestamp\": 1700000000")) + #expect(result.contains("\"avatarVersion\": \"00000000\"")) + + // The token addresses a conversation in every further request, and the fixture directory of its avatar is named after it, so it has to survive verbatim. + #expect(result.contains("\"token\":\"oypsiy6j\"")) + } + + @Test("Redacts the Talk signaling key") + func redactsSignalingKey() { + let body = "{\"hello-v2-token-key\": \"-----BEGIN PUBLIC KEY-----\\nMFkwEw==\\n-----END PUBLIC KEY-----\\n\"}" + let result = canonicalize(body, pathExtension: "json", path: "/ocs/v2.php/cloud/capabilities") + + // The key is generated per installation, and its name is unique enough to be replaced everywhere rather than only under the Talk API. + #expect(result.contains("\"hello-v2-token-key\": \"REDACTED\"")) + } + @Test("Redacts the volatile note entity tags") func redactsNoteEntityTags() { let body = #"[{"id":86,"modified":1700000000,"etag":"c649e503de046daca1b998c2e52b2a94"}]"# diff --git a/Tests/RainmakerTests/FixtureLocator.swift b/Tests/RainmakerTests/FixtureLocator.swift index 498d276..4d8736f 100644 --- a/Tests/RainmakerTests/FixtureLocator.swift +++ b/Tests/RainmakerTests/FixtureLocator.swift @@ -88,6 +88,9 @@ struct FixtureLocator { "json" case "application/xml": "xml" + case "*/*": + // A request announcing no preference asks for something which is not a payload this library parses, such as the image behind ``Server/conversationAvatar(_:darkTheme:)``. Recording it under the extension a download uses keeps such bytes out of ``FixtureCanonicalizer``, which skips that extension wholesale. + "bin" default: throw URLTestSessionError.unsupportedResponseType } diff --git a/Tests/RainmakerTests/ModelEncodingTests.swift b/Tests/RainmakerTests/ModelEncodingTests.swift index bd907c9..80fa9bf 100644 --- a/Tests/RainmakerTests/ModelEncodingTests.swift +++ b/Tests/RainmakerTests/ModelEncodingTests.swift @@ -117,6 +117,47 @@ import Testing #expect(keys == ["id", "order", "href", "icon", "type", "name", "app", "isActive", "unread", "classes", "isDefault"]) } + @Test("Conversation Encodes Under Its Property Names") + func conversation() throws { + let payload = """ + {"id":2,"token":"by9jp6mr","type":6,"name":"Note to self","displayName":"Note to self","description":"A place for your private notes","objectType":"note_to_self","objectId":"admin","participantType":1,"readOnly":0,"hasPassword":false,"hasCall":false,"lastActivity":1700000000,"lastReadMessage":5,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"notificationLevel":1,"sessionId":"0","permissions":510,"avatarVersion":"Rw2KnG5yXX5AdMzz","isCustomAvatar":true,"isArchived":false,"tagIds":[],"lastMessage":{"id":5,"message":"System created the conversation"}} + """ + + let keys = try encodedKeys(of: Conversation.self, from: payload) + + // The server sends far more about a conversation than is modelled, so what matters here is that none of it leaks into the encoded form. + #expect(keys.isDisjoint(with: ["name", "description", "objectType", "objectId", "participantType", "readOnly", "isFavorite", "isCustomAvatar", "lastMessage", "permissions", "tagIds"])) + #expect(keys == ["id", "token", "type", "displayName", "avatarVersion", "lastActivity", "unreadMessages", "unreadMention"]) + } + + @Test("Conversation Kind Encodes As Its Case Name") + func conversationType() throws { + let payload = """ + {"id":2,"token":"by9jp6mr","type":6,"displayName":"Note to self","lastActivity":1700000000,"unreadMessages":0,"unreadMention":false,"avatarVersion":"00000000"} + """ + + let conversation = try JSONDecoder().decode(Conversation.self, from: Data(payload.utf8)) + let data = try JSONEncoder().encode(conversation) + let object = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any]) + + // The raw number the server sends says nothing on its own, so the encoded form carries the name of the case instead. + #expect(object["type"] as? String == "noteToSelf") + } + + @Test("Unknown Conversation Kind Encodes As The Number The Server Sent") + func unknownConversationType() throws { + let payload = """ + {"id":9,"token":"aaaabbbb","type":99,"displayName":"From the future","lastActivity":1700000000,"unreadMessages":0,"unreadMention":false,"avatarVersion":"00000000"} + """ + + let conversation = try JSONDecoder().decode(Conversation.self, from: Data(payload.utf8)) + let data = try JSONEncoder().encode(conversation) + let object = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any]) + + // A kind this library cannot name has no name to encode, so the raw value survives rather than being lost. + #expect(object["type"] as? Int == 99) + } + @Test("Nested Activity Models Encode Under Their Property Names") func nestedActivityModels() throws { let payload = """ diff --git a/Tests/RainmakerTests/NavigationTests.swift b/Tests/RainmakerTests/NavigationTests.swift index a7cd304..8fdea9b 100644 --- a/Tests/RainmakerTests/NavigationTests.swift +++ b/Tests/RainmakerTests/NavigationTests.swift @@ -17,7 +17,7 @@ import Testing #expect(items.isEmpty == false) - // The "files" app is always advertised and is the default app, so it makes for a stable assertion. + // The "files" app is always advertised, so it makes for a stable assertion. let files = try #require(items.first { $0.id == "files" }) #expect(files.name == "Dateien") #expect(files.app == "files") @@ -28,7 +28,14 @@ import Testing #expect(files.unread == 0) #expect(files.classes == "") #expect(files.isActive == false) - #expect(files.isDefault) + + // The Talk app, which `FixtureOrchestrator` installs for `ConversationsTests`, registers itself ahead of everything else with a negative order and is therefore what the server reports as the default app. That is why the flag is asserted on the entry which actually carries it rather than on "files", which held it before Talk was part of the baseline. + #expect(files.isDefault == false) + + let talk = try #require(items.first { $0.id == "spreed" }) + #expect(talk.app == "spreed") + #expect(talk.order == -5) + #expect(talk.isDefault) } @Test("Unauthenticated Fetch", arguments: ServerVersion.allCases) diff --git a/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index d9a2c6d..a751c02 100644 --- a/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":31,"minor":0,"micro":14,"string":"31.0.14","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true},"bruteforce":{"delay":0,"allow-listed":false},"files":{"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"comments":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"5.0.2","text_processing":{"task_types":["free_prompt","headline","summary","topics"]}},"circles":{"version":"31.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}]},"dav":{"chunking":"1.0","bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"5.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.21.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jenna-kim-the-globe.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"},"user_status":{"enabled":true,"restore":true,"supports_emoji":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":31,"minor":0,"micro":14,"string":"31.0.14","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true},"bruteforce":{"delay":0,"allow-listed":false},"files":{"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"comments":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"5.0.2","text_processing":{"task_types":["free_prompt","headline","summary","topics"]}},"circles":{"version":"31.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files","talk"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}]},"dav":{"chunking":"1.0","bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"5.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.21.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","reactions"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations"],"config":{"attachments":{"allowed":true,"folder":"\/Talk"},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":true,"sip-enabled":false,"sip-dialout-enabled":false,"can-enable-sip":true,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"]},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100},"conversations":{"can-create":true,"force-passwords":false,"list-style":"two-lines","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0}},"config-local":{"attachments":["allowed","folder"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold"],"conversations":["can-create","list-style","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","hello-v2-token-key"],"experiments":["enabled"]},"version":"21.1.11"},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jenna-kim-the-globe.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"},"user_status":{"enabled":true,"restore":true,"supports_emoji":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index 0032ded..62940bd 100644 --- a/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/31.0.14/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":31,"minor":0,"micro":14,"string":"31.0.14","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"5.0.2"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jenna-kim-the-globe.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":31,"minor":0,"micro":14,"string":"31.0.14","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"5.0.2"},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","reactions"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations"],"config":{"attachments":{"allowed":false},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":false,"sip-enabled":false,"sip-dialout-enabled":false,"can-enable-sip":false,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"]},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100},"conversations":{"can-create":false,"force-passwords":false,"list-style":"two-lines","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0}},"config-local":{"attachments":["allowed","folder"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold"],"conversations":["can-create","list-style","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","hello-v2-token-key"],"experiments":["enabled"]},"version":"21.1.11"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jenna-kim-the-globe.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Body.bin b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Body.bin new file mode 100644 index 0000000..56a410b --- /dev/null +++ b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Body.bin @@ -0,0 +1,6 @@ + + + + + + diff --git a/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Headers.txt b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Headers.txt new file mode 100644 index 0000000..2a991a0 --- /dev/null +++ b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/3bpzwdam/avatar/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: image/svg+xml diff --git a/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..7901405 --- /dev/null +++ b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"x75r29cv","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":56,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":246,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":57,"token":"x75r29cv","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 21.1\n- Besprechungsunterhaltungen werden jetzt mit dem Titel und der Beschreibung aus dem Kalender synchronisiert und mit einem Suchfilter ausgeblendet, bis sie kurz vor dem Beginn stehen\n- Unterhaltungen in den Benachrichtigungseinstellungen als sensibel markieren, um den Inhalt der Nachricht aus der Unterhaltungsliste und den Benachrichtigungen auszublenden\n- Um Push-Benachrichtigungen w\u00e4hrend \"Bitte nicht st\u00f6ren\" zu erhalten, markieren Sie Unterhaltungen als wichtig\n- F\u00fcgen Sie andere Teilnehmer zu einem Einzelgespr\u00e4ch hinzu, um im Handumdrehen ein neues Gruppengespr\u00e4ch zu erstellen.","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true}},{"id":2,"token":"foyfmwuo","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":58,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":58,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":58,"token":"foyfmwuo","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false}},{"id":3,"token":"wxn97f7c","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":59,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":59,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":68,"token":"wxn97f7c","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true}},{"id":4,"token":"3bpzwdam","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":69,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":69,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":69,"token":"3bpzwdam","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..7901405 --- /dev/null +++ b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"x75r29cv","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":56,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":246,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":57,"token":"x75r29cv","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 21.1\n- Besprechungsunterhaltungen werden jetzt mit dem Titel und der Beschreibung aus dem Kalender synchronisiert und mit einem Suchfilter ausgeblendet, bis sie kurz vor dem Beginn stehen\n- Unterhaltungen in den Benachrichtigungseinstellungen als sensibel markieren, um den Inhalt der Nachricht aus der Unterhaltungsliste und den Benachrichtigungen auszublenden\n- Um Push-Benachrichtigungen w\u00e4hrend \"Bitte nicht st\u00f6ren\" zu erhalten, markieren Sie Unterhaltungen als wichtig\n- F\u00fcgen Sie andere Teilnehmer zu einem Einzelgespr\u00e4ch hinzu, um im Handumdrehen ein neues Gruppengespr\u00e4ch zu erstellen.","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true}},{"id":2,"token":"foyfmwuo","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":58,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":58,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":58,"token":"foyfmwuo","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false}},{"id":3,"token":"wxn97f7c","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":59,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":59,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":68,"token":"wxn97f7c","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true}},{"id":4,"token":"3bpzwdam","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":69,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":69,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":69,"token":"3bpzwdam","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/31.0.14/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/31.0.14/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json b/Tests/RainmakerTests/Responses/31.0.14/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json index 200f6d9..4d9a73f 100644 --- a/Tests/RainmakerTests/Responses/31.0.14/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json +++ b/Tests/RainmakerTests/Responses/31.0.14/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":true},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"spreed","name":"Talk","href":"http:\/\/localhost\/apps\/spreed\/","icon":"\/custom_apps\/spreed\/img\/app.svg","order":-5,"type":"link","active":false,"unread":0,"classes":"","app":"spreed","default":true},{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":false},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index 50e81a9..4e11e82 100644 --- a/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":32,"minor":0,"micro":11,"string":"32.0.11","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true,"user":{"language":"","locale":"","timezone":"UTC"}},"bruteforce":{"delay":0,"allow-listed":false},"files":{"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"windows_compatible_filenames":false,"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"comments":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"32.0.0"},"circles":{"version":"32.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"provider":"Nextcloud 32.0.11.1","resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}],"capabilities":["\/invite-accepted","\/notifications","\/shares"]},"dav":{"chunking":"1.0","public_shares_chunking":true,"search_supports_creation_time":true,"search_supports_upload_time":true,"search_supports_last_activity":true,"bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"5.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.22.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"},"user_status":{"enabled":true,"restore":true,"supports_emoji":true,"supports_busy":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":32,"minor":0,"micro":11,"string":"32.0.11","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true,"user":{"language":"","locale":"","timezone":"UTC"}},"bruteforce":{"delay":0,"allow-listed":false},"files":{"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"windows_compatible_filenames":false,"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"comments":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"32.0.0"},"circles":{"version":"32.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files","talk"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"provider":"Nextcloud 32.0.11.1","resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}],"capabilities":["\/invite-accepted","\/notifications","\/shares"]},"dav":{"chunking":"1.0","public_shares_chunking":true,"search_supports_creation_time":true,"search_supports_upload_time":true,"search_supports_last_activity":true,"bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"5.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.22.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","threads","federated-shared-items","reactions","recording-chunked-upload"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","recording-chunked-upload"],"config":{"attachments":{"allowed":true,"folder":"\/Talk"},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":true,"sip-enabled":false,"sip-dialout-enabled":false,"default-phone-region":"","can-enable-sip":true,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"live-transcription":false,"play-sounds":false,"grid-limit":19,"grid-limit-enforced":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"]},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100,"matterbridge-enabled":false},"conversations":{"can-create":true,"force-passwords":false,"list-style":"two-lines","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"mode":"internal","hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0},"permissions":{"max-default":254,"max-custom":255,"default":246}},"config-local":{"attachments":["allowed","folder"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background","play-sounds","grid-limit","grid-limit-enforced","external-call-service"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold","matterbridge-enabled"],"conversations":["can-create","list-style","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","hello-v2-token-key","mode"],"experiments":["enabled"],"permissions":[]},"version":"22.0.17"},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"},"user_status":{"enabled":true,"restore":true,"supports_emoji":true,"supports_busy":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index d8ded04..39bf0c9 100644 --- a/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/32.0.11/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":32,"minor":0,"micro":11,"string":"32.0.11","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"32.0.0"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":32,"minor":0,"micro":11,"string":"32.0.11","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"32.0.0"},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","threads","federated-shared-items","reactions","recording-chunked-upload"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","recording-chunked-upload"],"config":{"attachments":{"allowed":false},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":false,"sip-enabled":false,"sip-dialout-enabled":false,"default-phone-region":"","can-enable-sip":false,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"live-transcription":false,"play-sounds":true,"grid-limit":19,"grid-limit-enforced":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"]},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100,"matterbridge-enabled":false},"conversations":{"can-create":false,"force-passwords":false,"list-style":"two-lines","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"mode":"internal","hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0},"permissions":{"max-default":254,"max-custom":255,"default":246}},"config-local":{"attachments":["allowed","folder"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background","play-sounds","grid-limit","grid-limit-enforced","external-call-service"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold","matterbridge-enabled"],"conversations":["can-create","list-style","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","hello-v2-token-key","mode"],"experiments":["enabled"],"permissions":[]},"version":"22.0.17"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Body.bin b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Body.bin new file mode 100644 index 0000000..a023c30 --- /dev/null +++ b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Body.bin @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Headers.txt b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Headers.txt new file mode 100644 index 0000000..2a991a0 --- /dev/null +++ b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/smyfkdd6/avatar/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: image/svg+xml diff --git a/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..d01a713 --- /dev/null +++ b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"2cerbugc","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":57,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":246,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":58,"token":"2cerbugc","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 22\n- Themen verwenden, um Ihre Chats und Diskussionen zu organisieren\n- Live-Transkriptionen sind jetzt w\u00e4hrend des Anrufs verf\u00fcgbar (erfordert die Live-Transkriptions-ExApp und das Hochleistungs-Backend)","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":58}},{"id":2,"token":"4efznb8h","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":59,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":59,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":59,"token":"4efznb8h","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":59}},{"id":3,"token":"9e2h3j2v","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":60,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":60,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":69,"token":"9e2h3j2v","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":69}},{"id":4,"token":"smyfkdd6","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":70,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":70,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":70,"token":"smyfkdd6","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":70}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..d01a713 --- /dev/null +++ b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"2cerbugc","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":57,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":246,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":58,"token":"2cerbugc","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 22\n- Themen verwenden, um Ihre Chats und Diskussionen zu organisieren\n- Live-Transkriptionen sind jetzt w\u00e4hrend des Anrufs verf\u00fcgbar (erfordert die Live-Transkriptions-ExApp und das Hochleistungs-Backend)","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":58}},{"id":2,"token":"4efznb8h","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":59,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":59,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":59,"token":"4efznb8h","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":59}},{"id":3,"token":"9e2h3j2v","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":60,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":60,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":69,"token":"9e2h3j2v","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":69}},{"id":4,"token":"smyfkdd6","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":70,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":2,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":70,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","isArchived":false,"isImportant":false,"isSensitive":false,"lastMessage":{"id":70,"token":"smyfkdd6","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":70}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/32.0.11/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/32.0.11/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json b/Tests/RainmakerTests/Responses/32.0.11/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json index 200f6d9..4d9a73f 100644 --- a/Tests/RainmakerTests/Responses/32.0.11/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json +++ b/Tests/RainmakerTests/Responses/32.0.11/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":true},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"spreed","name":"Talk","href":"http:\/\/localhost\/apps\/spreed\/","icon":"\/custom_apps\/spreed\/img\/app.svg","order":-5,"type":"link","active":false,"unread":0,"classes":"","app":"spreed","default":true},{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":false},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index 341d748..20be44a 100644 --- a/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":33,"minor":0,"micro":5,"string":"33.0.5","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true,"user":{"language":"","locale":"","timezone":"UTC"},"can-create-app-token":true},"bruteforce":{"delay":0,"allow-listed":false},"files":{"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"windows_compatible_filenames":false,"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"comments":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"33.0.0"},"circles":{"version":"33.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"provider":"Nextcloud 33.0.5.1","resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}],"capabilities":["invite-accepted","notifications","shares"]},"dav":{"chunking":"1.0","public_shares_chunking":true,"search_supports_creation_time":true,"search_supports_upload_time":true,"search_supports_last_activity":true,"bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"exclude_reshare_from_edit":false,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"6.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.23.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"},"user_status":{"enabled":true,"restore":true,"supports_emoji":true,"supports_busy":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":33,"minor":0,"micro":5,"string":"33.0.5","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true,"user":{"language":"","locale":"","timezone":"UTC"},"can-create-app-token":true},"bruteforce":{"delay":0,"allow-listed":false},"files":{"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"windows_compatible_filenames":false,"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"comments":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"33.0.0"},"circles":{"version":"33.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files","talk"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"provider":"Nextcloud 33.0.5.1","resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}],"capabilities":["invite-accepted","notifications","shares"]},"dav":{"chunking":"1.0","public_shares_chunking":true,"search_supports_creation_time":true,"search_supports_upload_time":true,"search_supports_last_activity":true,"bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"exclude_reshare_from_edit":false,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"6.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.23.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","threads","pinned-messages","federated-shared-items","scheduled-messages","reactions","recording-chunked-upload"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","scheduled-messages","recording-chunked-upload"],"config":{"attachments":{"allowed":true,"folder":"\/Talk"},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":true,"sip-enabled":false,"sip-dialout-enabled":false,"default-phone-region":"","can-enable-sip":true,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"live-transcription":false,"live-translation":false,"play-sounds":true,"grid-limit":19,"grid-limit-enforced":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"],"live-transcription-target-language-id":""},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100,"style":"split","matterbridge-enabled":false},"conversations":{"can-create":true,"force-passwords":false,"list-style":"two-lines","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"mode":"internal","hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0},"permissions":{"max-default":254,"max-custom":255,"default":246}},"config-local":{"attachments":["allowed","folder"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background","live-transcription-target-language-id","play-sounds","grid-limit","grid-limit-enforced","external-call-service"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold","style","matterbridge-enabled"],"conversations":["can-create","list-style","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","hello-v2-token-key","mode"],"experiments":["enabled"],"permissions":[]},"version":"23.0.10"},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"},"user_status":{"enabled":true,"restore":true,"supports_emoji":true,"supports_busy":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index 1f43824..9a2c777 100644 --- a/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/33.0.5/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":33,"minor":0,"micro":5,"string":"33.0.5","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"33.0.0"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":33,"minor":0,"micro":5,"string":"33.0.5","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"33.0.0"},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","threads","pinned-messages","federated-shared-items","scheduled-messages","reactions","recording-chunked-upload"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","scheduled-messages","recording-chunked-upload"],"config":{"attachments":{"allowed":false},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":false,"sip-enabled":false,"sip-dialout-enabled":false,"default-phone-region":"","can-enable-sip":false,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"live-transcription":false,"live-translation":false,"play-sounds":true,"grid-limit":19,"grid-limit-enforced":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"],"live-transcription-target-language-id":""},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100,"style":"split","matterbridge-enabled":false},"conversations":{"can-create":false,"force-passwords":false,"list-style":"two-lines","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"mode":"internal","hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0},"permissions":{"max-default":254,"max-custom":255,"default":246}},"config-local":{"attachments":["allowed","folder"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background","live-transcription-target-language-id","play-sounds","grid-limit","grid-limit-enforced","external-call-service"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold","style","matterbridge-enabled"],"conversations":["can-create","list-style","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","hello-v2-token-key","mode"],"experiments":["enabled"],"permissions":[]},"version":"23.0.10"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0"}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Body.bin b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Body.bin new file mode 100644 index 0000000..a023c30 --- /dev/null +++ b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Body.bin @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Headers.txt b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Headers.txt new file mode 100644 index 0000000..2a991a0 --- /dev/null +++ b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/7nxqtfet/avatar/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: image/svg+xml diff --git a/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..93bac63 --- /dev/null +++ b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"5ui9sqco","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":3,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":246,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":4,"token":"5ui9sqco","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 23\n- die Moderation kann jetzt Nachrichten anheften, sodass diese oben bleiben\n- Den Versand einer Nachricht zu einem sp\u00e4teren Zeitpunkt planen","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":4}},{"id":2,"token":"268q367y","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":5,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":5,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":5,"token":"268q367y","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":5}},{"id":3,"token":"ddf7rn9a","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":6,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":6,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":15,"token":"ddf7rn9a","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":15}},{"id":4,"token":"7nxqtfet","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":16,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":16,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":16,"token":"7nxqtfet","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":16}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..93bac63 --- /dev/null +++ b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"5ui9sqco","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":3,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":246,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":4,"token":"5ui9sqco","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 23\n- die Moderation kann jetzt Nachrichten anheften, sodass diese oben bleiben\n- Den Versand einer Nachricht zu einem sp\u00e4teren Zeitpunkt planen","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":4}},{"id":2,"token":"268q367y","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":5,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":5,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":5,"token":"268q367y","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":5}},{"id":3,"token":"ddf7rn9a","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":6,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":6,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":15,"token":"ddf7rn9a","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":15}},{"id":4,"token":"7nxqtfet","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":16,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":254,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":16,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"hasScheduledMessages":0,"lastMessage":{"id":16,"token":"7nxqtfet","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":16}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/33.0.5/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/33.0.5/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json b/Tests/RainmakerTests/Responses/33.0.5/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json index 200f6d9..4d9a73f 100644 --- a/Tests/RainmakerTests/Responses/33.0.5/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json +++ b/Tests/RainmakerTests/Responses/33.0.5/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":true},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"spreed","name":"Talk","href":"http:\/\/localhost\/apps\/spreed\/","icon":"\/custom_apps\/spreed\/img\/app.svg","order":-5,"type":"link","active":false,"unread":0,"classes":"","app":"spreed","default":true},{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":false},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index 059402c..4fe0a7a 100644 --- a/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchAuthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":34,"minor":0,"micro":0,"string":"34.0.0","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true,"user":{"language":"","locale":"","timezone":"UTC"},"can-create-app-token":true},"bruteforce":{"delay":0,"allow-listed":false},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"34.0.0"},"circles":{"version":"34.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"provider":"Nextcloud 34.0.0.12","resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}],"capabilities":["invite-accepted","notifications","shares"]},"files":{"comments":true,"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"windows_compatible_filenames":false,"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"locking":"1.0","api-feature-lock-type":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"dav":{"chunking":"1.0","public_shares_chunking":true,"search_supports_creation_time":true,"search_supports_upload_time":true,"search_supports_last_activity":true,"bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"exclude_reshare_from_edit":false,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"minSearchStringLength":0,"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"6.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.24.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","imprintUrl":"","privacyUrl":"","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","primaryColor":"#00679e","backgroundColor":"#00679e","defaultPrimaryColor":"#00679e","defaultBackgroundColor":"#00679e","inverted":false,"cacheBuster":"e629f55f","enabledThemes":["default"]},"user_status":{"enabled":true,"restore":true,"supports_emoji":true,"supports_busy":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":34,"minor":0,"micro":0,"string":"34.0.0","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)","mod-rewrite-working":true,"user":{"language":"","locale":"","timezone":"UTC"},"can-create-app-token":true},"bruteforce":{"delay":0,"allow-listed":false},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"app_api":{"loglevel":2,"version":"34.0.0"},"circles":{"version":"34.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Einzeln","2":"Pers\u00f6nlich","4":"System","8":"Sichtbar","16":"Offen","32":"Einladen","64":"Beitrittsanfrage","128":"Freunde","256":"Passwortgesch\u00fctzt","512":"Kein Besitzer","1024":"Versteckt","2048":"Backend","4096":"Lokal","8192":"Root","16384":"Teameinladung","32768":"Federated","65536":"Einh\u00e4ngepunkt"},"source":{"core":{"1":"Nextcloud-Konto","2":"Nextcloud-Gruppe","4":"E-Mail-Adresse","8":"Kontakt","16":"Team","10000":"Nextcloud-App"},"extra":{"10001":"Teams-App","10002":"Administrations-Befehlszeile"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Mitglied","4":"Moderator","8":"Administration","9":"Eigent\u00fcmer"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}},"teamResourceProviders":["files","talk"]},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","version":"1.1.0","endPoint":"http:\/\/localhost\/ocm","publicKey":null,"provider":"Nextcloud 34.0.0.12","resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}],"capabilities":["invite-accepted","notifications","shares"]},"files":{"comments":true,"$comment":"\"blacklisted_files\" is deprecated as of Nextcloud 30, use \"forbidden_filenames\" instead","blacklisted_files":[".htaccess"],"forbidden_filenames":[".htaccess"],"forbidden_filename_basenames":[],"forbidden_filename_characters":["\\","\/"],"forbidden_filename_extensions":[".filepart",".part"],"bigfilechunking":true,"chunked_upload":{"max_size":104857600,"max_parallel_count":5},"file_conversions":[],"windows_compatible_filenames":false,"directEditing":{"url":"http:\/\/localhost\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag": "00000000000000000000000000000000","supportsFileId":true},"locking":"1.0","api-feature-lock-type":true,"undelete":true,"delete_from_trash":true,"versioning":true,"version_labeling":true,"version_deletion":true},"dav":{"chunking":"1.0","public_shares_chunking":true,"search_supports_creation_time":true,"search_supports_upload_time":true,"search_supports_last_activity":true,"bulkupload":"1.0","absence-supported":true,"absence-replacement":true},"downloadlimit":{"enabled":true,"default-limit":-1},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":false},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true,"custom_tokens":false},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"exclude_reshare_from_edit":false,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"minSearchStringLength":0,"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":false}}},"notes":{"api_version":["0.2","1.3","1.4"],"version":"6.0.2","notes_path":"Notizen"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists","test-push"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"provisioning_api":{"version":"1.24.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":false},"recommendations":{"enabled":true},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","react-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","threads","pinned-messages","federated-shared-items","scheduled-messages","conversation-presets","private-reply","conversation-tags","bot-features-api","reactions","recording-chunked-upload"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","scheduled-messages","conversation-presets","conversation-tags","recording-chunked-upload","bot-features-api"],"config":{"attachments":{"allowed":true,"conversation-subfolders":true,"folder":"\/Talk"},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":true,"sip-enabled":false,"sip-dialout-enabled":false,"default-phone-region":"","can-enable-sip":true,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"live-transcription":false,"live-translation":false,"play-sounds":true,"grid-limit":19,"grid-limit-enforced":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"],"live-transcription-target-language-id":""},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100,"style":"split","matterbridge-enabled":false},"conversations":{"can-create":true,"force-passwords":false,"list-style":"two-lines","sort-order":"activity","group-mode":"none","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"mode":"internal","hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0},"feature-hints":{"current":34,"hidden":0},"permissions":{"max-default":510,"max-custom":511,"default":502}},"config-local":{"attachments":["allowed","folder","conversation-subfolders"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background","live-transcription-target-language-id","play-sounds","grid-limit","grid-limit-enforced","external-call-service"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold","style","matterbridge-enabled"],"conversations":["can-create","list-style","sort-order","group-mode","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","mode","hello-v2-token-key"],"experiments":["enabled"],"feature-hints":["current","hidden"],"permissions":[]},"version":"24.0.4"},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","imprintUrl":"","privacyUrl":"","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","primaryColor":"#00679e","backgroundColor":"#00679e","defaultPrimaryColor":"#00679e","defaultBackgroundColor":"#00679e","inverted":false,"cacheBuster":"e629f55f","enabledThemes":["default"]},"user_status":{"enabled":true,"restore":true,"supports_emoji":true,"supports_busy":true},"weather_status":{"enabled":true}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json b/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json index 510f18c..f375cc3 100644 --- a/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json +++ b/Tests/RainmakerTests/Responses/34.0.0/CapabilitiesTests/fetchUnauthenticated/GET/ocs/v2.php/cloud/capabilities/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":34,"minor":0,"micro":0,"string":"34.0.0","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"34.0.0"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","imprintUrl":"","privacyUrl":"","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","primaryColor":"#00679e","backgroundColor":"#00679e","defaultPrimaryColor":"#00679e","defaultBackgroundColor":"#00679e","inverted":false,"cacheBuster":"9f817c33","enabledThemes":[]}}}}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":34,"minor":0,"micro":0,"string":"34.0.0","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0,"allow-listed":false},"app_api":{"version":"34.0.0"},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","react-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","delete-messages-unlimited","edit-messages","silent-send-state","chat-read-last","federation-v1","federation-v2","ban-v1","chat-reference-id","mention-permissions","edit-messages-note-to-self","archived-conversations-v2","talk-polls-drafts","download-call-participants","email-csv-import","conversation-creation-password","call-notification-state-api","schedule-meeting","edit-draft-poll","conversation-creation-all","important-conversations","unbind-conversation","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","threads","pinned-messages","federated-shared-items","scheduled-messages","conversation-presets","private-reply","conversation-tags","bot-features-api","reactions","recording-chunked-upload"],"features-local":["favorites","chat-read-status","listable-rooms","phonebook-search","temp-user-avatar-api","unified-search","avatar","remind-me-later","note-to-self","archived-conversations-v2","chat-summary-api","call-notification-state-api","schedule-meeting","conversation-creation-all","important-conversations","sip-direct-dialin","dashboard-event-rooms","mutual-calendar-events","upcoming-reminders","sensitive-conversations","scheduled-messages","conversation-presets","conversation-tags","recording-chunked-upload","bot-features-api"],"config":{"attachments":{"allowed":false,"conversation-subfolders":true},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4b","\ud83d\udc4d","\ud83d\udc4e","\ud83d\udd25","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"can-upload-background":false,"sip-enabled":false,"sip-dialout-enabled":false,"default-phone-region":"","can-enable-sip":false,"start-without-media":false,"max-duration":0,"blur-virtual-background":false,"end-to-end-encryption":false,"live-transcription":false,"live-translation":false,"play-sounds":true,"grid-limit":19,"grid-limit-enforced":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"predefined-backgrounds-v2":["\/custom_apps\/spreed\/img\/backgrounds\/1_office.jpg","\/custom_apps\/spreed\/img\/backgrounds\/2_home.jpg","\/custom_apps\/spreed\/img\/backgrounds\/3_abstract.jpg","\/custom_apps\/spreed\/img\/backgrounds\/4_beach.jpg","\/custom_apps\/spreed\/img\/backgrounds\/5_park.jpg","\/custom_apps\/spreed\/img\/backgrounds\/6_theater.jpg","\/custom_apps\/spreed\/img\/backgrounds\/7_library.jpg","\/custom_apps\/spreed\/img\/backgrounds\/8_space_station.jpg"],"live-transcription-target-language-id":""},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"has-translation-task-providers":false,"typing-privacy":0,"summary-threshold":100,"style":"split","matterbridge-enabled":false},"conversations":{"can-create":false,"force-passwords":false,"list-style":"two-lines","sort-order":"activity","group-mode":"none","description-length":2000,"retention-event":28,"retention-phone":7,"retention-instant-meetings":1},"federation":{"enabled":false,"incoming-enabled":false,"outgoing-enabled":false,"only-trusted-servers":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"mode":"internal","hello-v2-token-key": "REDACTED"},"experiments":{"enabled":0},"feature-hints":{"current":34,"hidden":0},"permissions":{"max-default":510,"max-custom":511,"default":502}},"config-local":{"attachments":["allowed","folder","conversation-subfolders"],"call":["predefined-backgrounds","predefined-backgrounds-v2","can-upload-background","start-without-media","blur-virtual-background","live-transcription-target-language-id","play-sounds","grid-limit","grid-limit-enforced","external-call-service"],"chat":["read-privacy","has-translation-providers","has-translation-task-providers","typing-privacy","summary-threshold","style","matterbridge-enabled"],"conversations":["can-create","list-style","sort-order","group-mode","description-length"],"federation":["enabled","incoming-enabled","outgoing-enabled","only-trusted-servers"],"previews":["max-gif-size"],"signaling":["session-ping-limit","mode","hello-v2-token-key"],"experiments":["enabled"],"feature-hints":["current","hidden"],"permissions":[]},"version":"24.0.4"},"theming":{"name":"Nextcloud","productName":"Nextcloud","url":"https:\/\/nextcloud.com","imprintUrl":"","privacyUrl":"","slogan":"ein sicherer Ort f\u00fcr all Ihre Daten","color":"#00679e","color-text":"#ffffff","color-element":"#00679e","color-element-bright":"#00679e","color-element-dark":"#00679e","logo":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","background":"http:\/\/localhost\/apps\/theming\/img\/background\/jo-myoung-hee-fluid.webp","background-text":"#ffffff","background-plain":false,"background-default":true,"logoheader":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","favicon":"http:\/\/localhost\/core\/img\/logo\/logo.svg?v=0","primaryColor":"#00679e","backgroundColor":"#00679e","defaultPrimaryColor":"#00679e","defaultBackgroundColor":"#00679e","inverted":false,"cacheBuster":"9f817c33","enabledThemes":[]}}}}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Body.bin b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Body.bin new file mode 100644 index 0000000..a023c30 --- /dev/null +++ b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Body.bin @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Headers.txt b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Headers.txt new file mode 100644 index 0000000..2a991a0 --- /dev/null +++ b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v1/room/75293txe/avatar/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: image/svg+xml diff --git a/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..53eff00 --- /dev/null +++ b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"nvjt2cky","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":3,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":502,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":4,"token":"nvjt2cky","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 24\n- Unterhaltungs-Schlagworte, Gruppierung und Sortierung zum Organisieren der Unterhaltungsliste\n- Unterhaltungs-R\u00e4ume um sich spontan zu verbinden\n- Voreinstellungen f\u00fcr eine schnelle Unterhaltungseinrichtung w\u00e4hrend der Erstellung\n- Direkte Anrufe aus Profilmen\u00fcs f\u00fcr Anrufe innerhalb anderer Apps","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":4}},{"id":2,"token":"ibxps9g5","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":5,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":510,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":5,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":5,"token":"ibxps9g5","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":5}},{"id":3,"token":"jotdo84c","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":6,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":510,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":6,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":15,"token":"jotdo84c","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":15}},{"id":4,"token":"75293txe","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":16,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":510,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":16,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":16,"token":"75293txe","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":16}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/avatar/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json new file mode 100644 index 0000000..53eff00 --- /dev/null +++ b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Body.json @@ -0,0 +1 @@ +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":1,"token":"nvjt2cky","type":4,"name":"admin","displayName":"Talk Aktualisierungen \u2705","objectType":"","objectId":"","participantType":3,"participantFlags":0,"readOnly":1,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":1,"unreadMessages":3,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":false,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":1,"permissions":502,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":1,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":4,"token":"nvjt2cky","actorType":"bots","actorId":"changelog","actorDisplayName":"Talk Aktualisierungen \u2705","timestamp": 1700000000,"message":"## Neu in Talk 24\n- Unterhaltungs-Schlagworte, Gruppierung und Sortierung zum Organisieren der Unterhaltungsliste\n- Unterhaltungs-R\u00e4ume um sich spontan zu verbinden\n- Voreinstellungen f\u00fcr eine schnelle Unterhaltungseinrichtung w\u00e4hrend der Erstellung\n- Direkte Anrufe aus Profilmen\u00fcs f\u00fcr Anrufe innerhalb anderer Apps","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":4}},{"id":2,"token":"ibxps9g5","type":6,"name":"Notiz an mich","displayName":"Notiz an mich","objectType":"note_to_self","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":false,"lastActivity": 1700000000,"lastReadMessage":5,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":true,"canLeaveConversation":false,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":2,"permissions":510,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"Ein Platz f\u00fcr Ihre privaten Notizen, Gedanken und Ideen","lastCommonReadMessage":5,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":5,"token":"ibxps9g5","actorType":"guests","actorId":"system","actorDisplayName":"","timestamp": 1700000000,"message":"Das System hat die Unterhaltung erstellt","messageParameters":{"actor":{"type":"guest","id":"guest\/system","name":"System","mention-id":"guest\/system"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":5}},{"id":3,"token":"jotdo84c","type":2,"name":"Los geht's!","displayName":"Los geht's!","objectType":"sample","objectId":"admin","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":6,"unreadMessages":7,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":3,"permissions":510,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"**Nextcloud Talk** ist eine sichere, selbst gehostete Kommunikationsplattform, die sich nahtlos in das Nextcloud-\u00d6kosystem integriert.\n\n#### Hauptmerkmale von Nextcloud Talk:\n\n* Chat und Messaging in privaten und Gruppenchats\n* Sprach- und Videoanrufe\n* Dateifreigabe und Integration mit anderen Nextcloud-Apps\n* Anpassbare Gespr\u00e4chseinstellungen, Moderation und Datenschutzkontrollen\n* Web, Desktop und Mobile (iOS und Android)\n* Private & sichere Kommunikation\n\n Erfahren Sie mehr in der [Benutzeranleitung](https:\/\/docs.nextcloud.com\/server\/latest\/user_manual\/en\/talk\/index.html).","lastCommonReadMessage":6,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":true,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":15,"token":"jotdo84c","actorType":"bots","actorId":"sample","actorDisplayName":"Nextcloud","timestamp": 1700000000,"message":"## \u2699\ufe0f Gespr\u00e4chseinstellungen verwalten\n\nIm Men\u00fc \"Unterhaltung\" k\u00f6nnen Sie auf verschiedene Einstellungen zur Verwaltung Ihrer Unterhaltungen zugreifen, wie z. B.:\n* Unterhaltungsinformationen bearbeiten\n* Verwalten von Benachrichtigungen\n* Zahlreiche Moderationsregeln anwenden\n* Zugang und Sicherheit konfigurieren\n* Aktivieren von Bots\n* und mehr!","messageParameters":[],"systemMessage":"","messageType":"comment","isReplyable":true,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":true,"threadId":15}},{"id":4,"token":"75293txe","type":2,"name":"Rainmaker","displayName":"Rainmaker","objectType":"","objectId":"","participantType":1,"participantFlags":0,"readOnly":0,"hasPassword":false,"hasCall":false,"callStartTime":0,"callRecording":0,"canStartCall":true,"lastActivity": 1700000000,"lastReadMessage":16,"unreadMessages":0,"unreadMention":false,"unreadMentionDirect":false,"isFavorite":false,"canLeaveConversation":true,"canDeleteConversation":true,"notificationLevel":1,"notificationCalls":1,"lobbyState":0,"lobbyTimer":0,"lastPing":0,"sessionId":"0","sipEnabled":0,"actorType":"users","actorId":"admin","attendeeId":4,"permissions":510,"attendeePermissions":0,"callPermissions":0,"defaultPermissions":0,"canEnableSIP":false,"attendeePin":"","description":"","lastCommonReadMessage":16,"listable":0,"callFlag":0,"messageExpiration":0,"avatarVersion": "00000000","isCustomAvatar":false,"breakoutRoomMode":0,"breakoutRoomStatus":0,"recordingConsent":0,"mentionPermissions":0,"liveTranscriptionLanguageId":"","lastPinnedId":0,"hiddenPinnedId":0,"isArchived":false,"isImportant":false,"isSensitive":false,"tagIds":[],"hasScheduledMessages":0,"attributes":0,"lastMessage":{"id":16,"token":"75293txe","actorType":"users","actorId":"admin","actorDisplayName":"admin","timestamp": 1700000000,"message":"Sie haben die Unterhaltung erstellt","messageParameters":{"actor":{"type":"user","id":"admin","name":"admin","mention-id":"admin"}},"systemMessage":"conversation_created","messageType":"system","isReplyable":false,"referenceId":"","reactions":{},"expirationTimestamp":0,"markdown":false,"threadId":16}}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt new file mode 100644 index 0000000..e18b7e2 --- /dev/null +++ b/Tests/RainmakerTests/Responses/34.0.0/ConversationsTests/fetch/GET/ocs/v2.php/apps/spreed/api/v4/room/Headers.txt @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 diff --git a/Tests/RainmakerTests/Responses/34.0.0/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json b/Tests/RainmakerTests/Responses/34.0.0/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json index d23373e..4a4d248 100644 --- a/Tests/RainmakerTests/Responses/34.0.0/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json +++ b/Tests/RainmakerTests/Responses/34.0.0/NavigationTests/fetchAuthenticated/GET/ocs/v2.php/core/navigation/apps/Body.json @@ -1 +1 @@ -{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":true},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false},{"id":"office","order":100,"href":"\/apps\/office\/","icon":"\/apps\/office\/img\/app.svg","type":"link","name":"Office","app":"office","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file +{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":[{"id":"spreed","name":"Talk","href":"http:\/\/localhost\/apps\/spreed\/","icon":"\/custom_apps\/spreed\/img\/app.svg","order":-5,"type":"link","active":false,"unread":0,"classes":"","app":"spreed","default":true},{"id":"files","order":0,"href":"\/apps\/files\/","icon":"\/apps\/files\/img\/app.svg","type":"link","name":"Dateien","app":"files","active":false,"unread":0,"classes":"","default":false},{"id":"photos","order":1,"href":"\/apps\/photos\/","icon":"\/apps\/photos\/img\/photos.svg","type":"link","name":"Fotos","app":"photos","active":false,"unread":0,"classes":"","default":false},{"id":"activity","order":1,"href":"\/apps\/activity\/","icon":"\/apps\/activity\/img\/activity.svg","type":"link","name":"Aktivit\u00e4t","app":"activity","active":false,"unread":0,"classes":"","default":false},{"id":"notes","order":10,"href":"\/apps\/notes\/","icon":"\/custom_apps\/notes\/img\/notes.svg","type":"link","name":"Notizen","app":"notes","active":false,"unread":0,"classes":"","default":false},{"id":"office","order":100,"href":"\/apps\/office\/","icon":"\/apps\/office\/img\/app.svg","type":"link","name":"Office","app":"office","active":false,"unread":0,"classes":"","default":false}]}} \ No newline at end of file diff --git a/Tests/RainmakerTests/URLRecordingSession.swift b/Tests/RainmakerTests/URLRecordingSession.swift index 0d60c0a..d2682bd 100644 --- a/Tests/RainmakerTests/URLRecordingSession.swift +++ b/Tests/RainmakerTests/URLRecordingSession.swift @@ -65,7 +65,7 @@ public actor URLRecordingSession: Requesting { } let bodyExtension = try FixtureLocator.bodyExtension(forAcceptHeader: acceptedType) - let body = canonicalizer.canonicalizedBody(data, pathExtension: bodyExtension) + let body = canonicalizer.canonicalizedBody(data, pathExtension: bodyExtension, requestURL: url) try record(method: method, url: url, response: response, body: body, bodyExtension: bodyExtension) return (data, urlResponse) @@ -76,7 +76,7 @@ public actor URLRecordingSession: Requesting { let (method, url, response) = try Self.components(of: request, urlResponse) let data = try Data(contentsOf: location) - let body = canonicalizer.canonicalizedBody(data, pathExtension: "bin") + let body = canonicalizer.canonicalizedBody(data, pathExtension: "bin", requestURL: url) try record(method: method, url: url, response: response, body: body, bodyExtension: "bin") return (location, urlResponse)