From 4842304d2675232610dc099a6fc0e5085697c088 Mon Sep 17 00:00:00 2001 From: Gordan Ovcaric Date: Tue, 21 Apr 2026 13:58:31 +0200 Subject: [PATCH] TT-4429: CRUDs for rules/policies/lists, new feature --- .claude/settings.local.json | 7 +- src/main/kotlin/com/nylas/NylasClient.kt | 18 + .../nylas/models/CreateNylasListRequest.kt | 49 +++ .../com/nylas/models/CreatePolicyRequest.kt | 81 +++++ .../com/nylas/models/CreateRuleRequest.kt | 89 +++++ .../com/nylas/models/ListItemsRequest.kt | 14 + .../models/ListNylasListItemsQueryParams.kt | 32 ++ .../nylas/models/ListNylasListsQueryParams.kt | 32 ++ .../nylas/models/ListPoliciesQueryParams.kt | 32 ++ .../com/nylas/models/ListRulesQueryParams.kt | 32 ++ src/main/kotlin/com/nylas/models/NylasList.kt | 55 +++ .../kotlin/com/nylas/models/NylasListItem.kt | 29 ++ .../kotlin/com/nylas/models/NylasListType.kt | 26 ++ src/main/kotlin/com/nylas/models/Policy.kt | 59 ++++ .../kotlin/com/nylas/models/PolicyLimits.kt | 49 +++ .../kotlin/com/nylas/models/PolicyOptions.kt | 19 + .../com/nylas/models/PolicySpamDetection.kt | 24 ++ src/main/kotlin/com/nylas/models/Rule.kt | 69 ++++ .../kotlin/com/nylas/models/RuleAction.kt | 19 + .../kotlin/com/nylas/models/RuleActionType.kt | 52 +++ .../kotlin/com/nylas/models/RuleCondition.kt | 26 ++ .../com/nylas/models/RuleConditionOperator.kt | 23 ++ src/main/kotlin/com/nylas/models/RuleMatch.kt | 19 + .../com/nylas/models/RuleMatchOperator.kt | 20 ++ .../kotlin/com/nylas/models/RuleTrigger.kt | 20 ++ .../nylas/models/UpdateNylasListRequest.kt | 48 +++ .../com/nylas/models/UpdatePolicyRequest.kt | 86 +++++ .../com/nylas/models/UpdateRuleRequest.kt | 112 ++++++ .../kotlin/com/nylas/resources/NylasLists.kt | 142 ++++++++ .../kotlin/com/nylas/resources/Policies.kt | 81 +++++ src/main/kotlin/com/nylas/resources/Rules.kt | 81 +++++ .../com/nylas/resources/NylasListsTests.kt | 326 ++++++++++++++++++ .../com/nylas/resources/PoliciesTests.kt | 304 ++++++++++++++++ .../kotlin/com/nylas/resources/RulesTests.kt | 326 ++++++++++++++++++ 34 files changed, 2400 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/nylas/models/CreateNylasListRequest.kt create mode 100644 src/main/kotlin/com/nylas/models/CreatePolicyRequest.kt create mode 100644 src/main/kotlin/com/nylas/models/CreateRuleRequest.kt create mode 100644 src/main/kotlin/com/nylas/models/ListItemsRequest.kt create mode 100644 src/main/kotlin/com/nylas/models/ListNylasListItemsQueryParams.kt create mode 100644 src/main/kotlin/com/nylas/models/ListNylasListsQueryParams.kt create mode 100644 src/main/kotlin/com/nylas/models/ListPoliciesQueryParams.kt create mode 100644 src/main/kotlin/com/nylas/models/ListRulesQueryParams.kt create mode 100644 src/main/kotlin/com/nylas/models/NylasList.kt create mode 100644 src/main/kotlin/com/nylas/models/NylasListItem.kt create mode 100644 src/main/kotlin/com/nylas/models/NylasListType.kt create mode 100644 src/main/kotlin/com/nylas/models/Policy.kt create mode 100644 src/main/kotlin/com/nylas/models/PolicyLimits.kt create mode 100644 src/main/kotlin/com/nylas/models/PolicyOptions.kt create mode 100644 src/main/kotlin/com/nylas/models/PolicySpamDetection.kt create mode 100644 src/main/kotlin/com/nylas/models/Rule.kt create mode 100644 src/main/kotlin/com/nylas/models/RuleAction.kt create mode 100644 src/main/kotlin/com/nylas/models/RuleActionType.kt create mode 100644 src/main/kotlin/com/nylas/models/RuleCondition.kt create mode 100644 src/main/kotlin/com/nylas/models/RuleConditionOperator.kt create mode 100644 src/main/kotlin/com/nylas/models/RuleMatch.kt create mode 100644 src/main/kotlin/com/nylas/models/RuleMatchOperator.kt create mode 100644 src/main/kotlin/com/nylas/models/RuleTrigger.kt create mode 100644 src/main/kotlin/com/nylas/models/UpdateNylasListRequest.kt create mode 100644 src/main/kotlin/com/nylas/models/UpdatePolicyRequest.kt create mode 100644 src/main/kotlin/com/nylas/models/UpdateRuleRequest.kt create mode 100644 src/main/kotlin/com/nylas/resources/NylasLists.kt create mode 100644 src/main/kotlin/com/nylas/resources/Policies.kt create mode 100644 src/main/kotlin/com/nylas/resources/Rules.kt create mode 100644 src/test/kotlin/com/nylas/resources/NylasListsTests.kt create mode 100644 src/test/kotlin/com/nylas/resources/PoliciesTests.kt create mode 100644 src/test/kotlin/com/nylas/resources/RulesTests.kt diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0165ab35..abee2994 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,12 @@ "Bash(/usr/libexec/java_home:*)", "Bash(./gradlew clean test:*)", "Bash(./gradlew:*)", - "Bash(./gradlew build:*)" + "Bash(./gradlew build:*)", + "mcp__claude_ai_Slack__slack_read_thread", + "WebFetch(domain:developer.nylas.com)", + "mcp__claude_ai_Atlassian__getAccessibleAtlassianResources", + "mcp__claude_ai_Atlassian__getJiraIssue", + "WebSearch" ] } } diff --git a/src/main/kotlin/com/nylas/NylasClient.kt b/src/main/kotlin/com/nylas/NylasClient.kt index dd6130b8..48a6cd84 100644 --- a/src/main/kotlin/com/nylas/NylasClient.kt +++ b/src/main/kotlin/com/nylas/NylasClient.kt @@ -164,6 +164,24 @@ open class NylasClient( */ open fun notetakers(): Notetakers = Notetakers(this) + /** + * Access the Policies API + * @return The Policies API + */ + open fun policies(): Policies = Policies(this) + + /** + * Access the Rules API + * @return The Rules API + */ + open fun rules(): Rules = Rules(this) + + /** + * Access the Lists API + * @return The Lists API + */ + open fun lists(): NylasLists = NylasLists(this) + /** * Get a URL builder instance for the Nylas API. */ diff --git a/src/main/kotlin/com/nylas/models/CreateNylasListRequest.kt b/src/main/kotlin/com/nylas/models/CreateNylasListRequest.kt new file mode 100644 index 00000000..e529c190 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/CreateNylasListRequest.kt @@ -0,0 +1,49 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas create list request. + */ +data class CreateNylasListRequest( + /** + * Name of the list (1–256 characters). + */ + @Json(name = "name") + val name: String, + /** + * The type of values this list will hold. Immutable after creation. + */ + @Json(name = "type") + val type: NylasListType, + /** + * Optional description of the list. + */ + @Json(name = "description") + val description: String? = null, +) { + /** + * Builder for [CreateNylasListRequest]. + * @param name Name of the list. + * @param type The type of values this list will hold. + */ + data class Builder( + private val name: String, + private val type: NylasListType, + ) { + private var description: String? = null + + /** + * Set the description of the list. + * @param description Optional description of the list. + * @return The builder. + */ + fun description(description: String) = apply { this.description = description } + + /** + * Build the [CreateNylasListRequest]. + * @return A [CreateNylasListRequest] with the provided values. + */ + fun build() = CreateNylasListRequest(name, type, description) + } +} diff --git a/src/main/kotlin/com/nylas/models/CreatePolicyRequest.kt b/src/main/kotlin/com/nylas/models/CreatePolicyRequest.kt new file mode 100644 index 00000000..3a4c6510 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/CreatePolicyRequest.kt @@ -0,0 +1,81 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas create policy request. + */ +data class CreatePolicyRequest( + /** + * Name of the policy. + */ + @Json(name = "name") + val name: String, + /** + * Optional mailbox and behavior settings. + */ + @Json(name = "options") + val options: PolicyOptions? = null, + /** + * Resource and rate limits for agent accounts using this policy. + */ + @Json(name = "limits") + val limits: PolicyLimits? = null, + /** + * IDs of rules to link to this policy. + */ + @Json(name = "rules") + val rules: List? = null, + /** + * Spam detection configuration. + */ + @Json(name = "spam_detection") + val spamDetection: PolicySpamDetection? = null, +) { + /** + * Builder for [CreatePolicyRequest]. + * @param name Name of the policy. + */ + data class Builder( + private val name: String, + ) { + private var options: PolicyOptions? = null + private var limits: PolicyLimits? = null + private var rules: List? = null + private var spamDetection: PolicySpamDetection? = null + + /** + * Set the mailbox and behavior settings. + * @param options Optional mailbox and behavior settings. + * @return The builder. + */ + fun options(options: PolicyOptions) = apply { this.options = options } + + /** + * Set the resource and rate limits. + * @param limits Resource and rate limits for agent accounts using this policy. + * @return The builder. + */ + fun limits(limits: PolicyLimits) = apply { this.limits = limits } + + /** + * Set the IDs of rules to link to this policy. + * @param rules IDs of rules to link to this policy. + * @return The builder. + */ + fun rules(rules: List) = apply { this.rules = rules } + + /** + * Set the spam detection configuration. + * @param spamDetection Spam detection configuration. + * @return The builder. + */ + fun spamDetection(spamDetection: PolicySpamDetection) = apply { this.spamDetection = spamDetection } + + /** + * Build the [CreatePolicyRequest]. + * @return A [CreatePolicyRequest] with the provided values. + */ + fun build() = CreatePolicyRequest(name, options, limits, rules, spamDetection) + } +} diff --git a/src/main/kotlin/com/nylas/models/CreateRuleRequest.kt b/src/main/kotlin/com/nylas/models/CreateRuleRequest.kt new file mode 100644 index 00000000..1555a75f --- /dev/null +++ b/src/main/kotlin/com/nylas/models/CreateRuleRequest.kt @@ -0,0 +1,89 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas create rule request. + */ +data class CreateRuleRequest( + /** + * Name of the rule. + */ + @Json(name = "name") + val name: String, + /** + * When this rule is evaluated. + */ + @Json(name = "trigger") + val trigger: RuleTrigger, + /** + * The match conditions for this rule. + */ + @Json(name = "match") + val match: RuleMatch, + /** + * The actions to perform when conditions are met. + */ + @Json(name = "actions") + val actions: List, + /** + * Optional description of the rule. + */ + @Json(name = "description") + val description: String? = null, + /** + * Evaluation order — lower numbers run first. Range 0–1000, default 10. + */ + @Json(name = "priority") + val priority: Int? = null, + /** + * Whether the rule is active. Defaults to true. + */ + @Json(name = "enabled") + val enabled: Boolean? = null, +) { + /** + * Builder for [CreateRuleRequest]. + * @param name Name of the rule. + * @param trigger When this rule is evaluated. + * @param match The match conditions. + * @param actions The actions to perform. + */ + data class Builder( + private val name: String, + private val trigger: RuleTrigger, + private val match: RuleMatch, + private val actions: List, + ) { + private var description: String? = null + private var priority: Int? = null + private var enabled: Boolean? = null + + /** + * Set the description of the rule. + * @param description Optional description of the rule. + * @return The builder. + */ + fun description(description: String) = apply { this.description = description } + + /** + * Set the evaluation priority. + * @param priority Evaluation order — lower numbers run first. Range 0–1000. + * @return The builder. + */ + fun priority(priority: Int) = apply { this.priority = priority } + + /** + * Set whether the rule is active. + * @param enabled Whether the rule is active. + * @return The builder. + */ + fun enabled(enabled: Boolean) = apply { this.enabled = enabled } + + /** + * Build the [CreateRuleRequest]. + * @return A [CreateRuleRequest] with the provided values. + */ + fun build() = CreateRuleRequest(name, trigger, match, actions, description, priority, enabled) + } +} diff --git a/src/main/kotlin/com/nylas/models/ListItemsRequest.kt b/src/main/kotlin/com/nylas/models/ListItemsRequest.kt new file mode 100644 index 00000000..911f36e3 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/ListItemsRequest.kt @@ -0,0 +1,14 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a request to add or remove items in a Nylas list. + */ +data class ListItemsRequest( + /** + * The values to add or remove. Max 1000 items per request. + */ + @Json(name = "items") + val items: List, +) diff --git a/src/main/kotlin/com/nylas/models/ListNylasListItemsQueryParams.kt b/src/main/kotlin/com/nylas/models/ListNylasListItemsQueryParams.kt new file mode 100644 index 00000000..1f05c02d --- /dev/null +++ b/src/main/kotlin/com/nylas/models/ListNylasListItemsQueryParams.kt @@ -0,0 +1,32 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of the query parameters for listing items in a Nylas list. + */ +data class ListNylasListItemsQueryParams( + /** + * The maximum number of objects to return. + */ + @Json(name = "limit") + val limit: Int? = null, + /** + * Cursor for pagination. Pass the value of [ListResponse.nextCursor] to get the next page. + */ + @Json(name = "page_token") + val pageToken: String? = null, +) : IQueryParams { + /** + * Builder for [ListNylasListItemsQueryParams]. + */ + class Builder { + private var limit: Int? = null + private var pageToken: String? = null + + fun limit(limit: Int) = apply { this.limit = limit } + fun pageToken(pageToken: String) = apply { this.pageToken = pageToken } + + fun build() = ListNylasListItemsQueryParams(limit, pageToken) + } +} diff --git a/src/main/kotlin/com/nylas/models/ListNylasListsQueryParams.kt b/src/main/kotlin/com/nylas/models/ListNylasListsQueryParams.kt new file mode 100644 index 00000000..dd5f9b66 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/ListNylasListsQueryParams.kt @@ -0,0 +1,32 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of the query parameters for listing Nylas lists. + */ +data class ListNylasListsQueryParams( + /** + * The maximum number of objects to return. + */ + @Json(name = "limit") + val limit: Int? = null, + /** + * Cursor for pagination. Pass the value of [ListResponse.nextCursor] to get the next page. + */ + @Json(name = "page_token") + val pageToken: String? = null, +) : IQueryParams { + /** + * Builder for [ListNylasListsQueryParams]. + */ + class Builder { + private var limit: Int? = null + private var pageToken: String? = null + + fun limit(limit: Int) = apply { this.limit = limit } + fun pageToken(pageToken: String) = apply { this.pageToken = pageToken } + + fun build() = ListNylasListsQueryParams(limit, pageToken) + } +} diff --git a/src/main/kotlin/com/nylas/models/ListPoliciesQueryParams.kt b/src/main/kotlin/com/nylas/models/ListPoliciesQueryParams.kt new file mode 100644 index 00000000..9a108a84 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/ListPoliciesQueryParams.kt @@ -0,0 +1,32 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of the query parameters for listing policies. + */ +data class ListPoliciesQueryParams( + /** + * The maximum number of objects to return. + */ + @Json(name = "limit") + val limit: Int? = null, + /** + * Cursor for pagination. Pass the value of [ListResponse.nextCursor] to get the next page. + */ + @Json(name = "page_token") + val pageToken: String? = null, +) : IQueryParams { + /** + * Builder for [ListPoliciesQueryParams]. + */ + class Builder { + private var limit: Int? = null + private var pageToken: String? = null + + fun limit(limit: Int) = apply { this.limit = limit } + fun pageToken(pageToken: String) = apply { this.pageToken = pageToken } + + fun build() = ListPoliciesQueryParams(limit, pageToken) + } +} diff --git a/src/main/kotlin/com/nylas/models/ListRulesQueryParams.kt b/src/main/kotlin/com/nylas/models/ListRulesQueryParams.kt new file mode 100644 index 00000000..703608ae --- /dev/null +++ b/src/main/kotlin/com/nylas/models/ListRulesQueryParams.kt @@ -0,0 +1,32 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of the query parameters for listing rules. + */ +data class ListRulesQueryParams( + /** + * The maximum number of objects to return. + */ + @Json(name = "limit") + val limit: Int? = null, + /** + * Cursor for pagination. Pass the value of [ListResponse.nextCursor] to get the next page. + */ + @Json(name = "page_token") + val pageToken: String? = null, +) : IQueryParams { + /** + * Builder for [ListRulesQueryParams]. + */ + class Builder { + private var limit: Int? = null + private var pageToken: String? = null + + fun limit(limit: Int) = apply { this.limit = limit } + fun pageToken(pageToken: String) = apply { this.pageToken = pageToken } + + fun build() = ListRulesQueryParams(limit, pageToken) + } +} diff --git a/src/main/kotlin/com/nylas/models/NylasList.kt b/src/main/kotlin/com/nylas/models/NylasList.kt new file mode 100644 index 00000000..ebf1cd24 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/NylasList.kt @@ -0,0 +1,55 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas list object. + * Lists are typed collections of domains, TLDs, or addresses referenced by rule conditions. + */ +data class NylasList( + /** + * Globally unique object identifier. + */ + @Json(name = "id") + val id: String = "", + /** + * Name of the list. + */ + @Json(name = "name") + val name: String = "", + /** + * Optional description of the list. + */ + @Json(name = "description") + val description: String? = null, + /** + * The type of values stored in this list. Immutable after creation. + */ + @Json(name = "type") + val type: NylasListType = NylasListType.DOMAIN, + /** + * The number of items currently in the list. + */ + @Json(name = "items_count") + val itemsCount: Int = 0, + /** + * The ID of the Nylas application this list belongs to. + */ + @Json(name = "application_id") + val applicationId: String = "", + /** + * The ID of the organization this list belongs to. + */ + @Json(name = "organization_id") + val organizationId: String = "", + /** + * Unix timestamp when the list was created. + */ + @Json(name = "created_at") + val createdAt: Long = 0, + /** + * Unix timestamp when the list was last updated. + */ + @Json(name = "updated_at") + val updatedAt: Long = 0, +) diff --git a/src/main/kotlin/com/nylas/models/NylasListItem.kt b/src/main/kotlin/com/nylas/models/NylasListItem.kt new file mode 100644 index 00000000..0f04967a --- /dev/null +++ b/src/main/kotlin/com/nylas/models/NylasListItem.kt @@ -0,0 +1,29 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a single item in a Nylas list. + */ +data class NylasListItem( + /** + * Globally unique object identifier. + */ + @Json(name = "id") + val id: String = "", + /** + * The ID of the list this item belongs to. + */ + @Json(name = "list_id") + val listId: String = "", + /** + * The value stored in this item (domain, TLD, or address depending on list type). + */ + @Json(name = "value") + val value: String = "", + /** + * Unix timestamp when the item was created. + */ + @Json(name = "created_at") + val createdAt: Long = 0, +) diff --git a/src/main/kotlin/com/nylas/models/NylasListType.kt b/src/main/kotlin/com/nylas/models/NylasListType.kt new file mode 100644 index 00000000..3c98e4fb --- /dev/null +++ b/src/main/kotlin/com/nylas/models/NylasListType.kt @@ -0,0 +1,26 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * The type of values stored in a Nylas list. + */ +enum class NylasListType { + /** + * The list contains domain names (e.g. example.com). + */ + @Json(name = "domain") + DOMAIN, + + /** + * The list contains top-level domains (e.g. xyz, com). + */ + @Json(name = "tld") + TLD, + + /** + * The list contains email addresses (e.g. user@example.com). + */ + @Json(name = "address") + ADDRESS, +} diff --git a/src/main/kotlin/com/nylas/models/Policy.kt b/src/main/kotlin/com/nylas/models/Policy.kt new file mode 100644 index 00000000..e7b3ba41 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/Policy.kt @@ -0,0 +1,59 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas policy object. + */ +data class Policy( + /** + * Globally unique object identifier. + */ + @Json(name = "id") + val id: String = "", + /** + * Name of the policy. + */ + @Json(name = "name") + val name: String = "", + /** + * The ID of the Nylas application this policy belongs to. + */ + @Json(name = "application_id") + val applicationId: String = "", + /** + * The ID of the organization this policy belongs to. + */ + @Json(name = "organization_id") + val organizationId: String = "", + /** + * Optional mailbox and behavior settings. + */ + @Json(name = "options") + val options: PolicyOptions? = null, + /** + * Resource and rate limits for agent accounts using this policy. + */ + @Json(name = "limits") + val limits: PolicyLimits? = null, + /** + * IDs of rules linked to this policy. + */ + @Json(name = "rules") + val rules: List? = null, + /** + * Spam detection configuration. + */ + @Json(name = "spam_detection") + val spamDetection: PolicySpamDetection? = null, + /** + * Unix timestamp when the policy was created. + */ + @Json(name = "created_at") + val createdAt: Long = 0, + /** + * Unix timestamp when the policy was last updated. + */ + @Json(name = "updated_at") + val updatedAt: Long = 0, +) diff --git a/src/main/kotlin/com/nylas/models/PolicyLimits.kt b/src/main/kotlin/com/nylas/models/PolicyLimits.kt new file mode 100644 index 00000000..d33dfcd1 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/PolicyLimits.kt @@ -0,0 +1,49 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas policy limits object. + */ +data class PolicyLimits( + /** + * Maximum size in bytes for a single attachment. + */ + @Json(name = "limit_attachment_size_limit") + val attachmentSizeLimit: Long? = null, + /** + * Maximum number of attachments per message. + */ + @Json(name = "limit_attachment_count_limit") + val attachmentCountLimit: Int? = null, + /** + * Allowed MIME types for attachments. + */ + @Json(name = "limit_attachment_allowed_types") + val attachmentAllowedTypes: List? = null, + /** + * Maximum total MIME size in bytes per message. + */ + @Json(name = "limit_size_total_mime") + val sizeTotalMime: Long? = null, + /** + * Maximum total storage in bytes for the agent account. + */ + @Json(name = "limit_storage_total") + val storageTotal: Long? = null, + /** + * Maximum number of messages the agent account can send per day. + */ + @Json(name = "limit_count_daily_message_per_grant") + val countDailyMessagePerGrant: Int? = null, + /** + * Number of days to retain messages in the inbox. + */ + @Json(name = "limit_inbox_retention_period") + val inboxRetentionPeriod: Int? = null, + /** + * Number of days to retain messages in the spam folder. + */ + @Json(name = "limit_spam_retention_period") + val spamRetentionPeriod: Int? = null, +) diff --git a/src/main/kotlin/com/nylas/models/PolicyOptions.kt b/src/main/kotlin/com/nylas/models/PolicyOptions.kt new file mode 100644 index 00000000..a510489d --- /dev/null +++ b/src/main/kotlin/com/nylas/models/PolicyOptions.kt @@ -0,0 +1,19 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas policy options object. + */ +data class PolicyOptions( + /** + * Additional folders to create in the agent account mailbox. + */ + @Json(name = "additional_folders") + val additionalFolders: List? = null, + /** + * If true, enables CIDR aliasing for IP-based allow/block rules. + */ + @Json(name = "use_cidr_aliasing") + val useCidrAliasing: Boolean? = null, +) diff --git a/src/main/kotlin/com/nylas/models/PolicySpamDetection.kt b/src/main/kotlin/com/nylas/models/PolicySpamDetection.kt new file mode 100644 index 00000000..dd821b80 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/PolicySpamDetection.kt @@ -0,0 +1,24 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas policy spam detection configuration. + */ +data class PolicySpamDetection( + /** + * If true, enables DNS-based blocklist (DNSBL) checks. + */ + @Json(name = "use_list_dnsbl") + val useListDnsbl: Boolean? = null, + /** + * If true, enables header anomaly detection. + */ + @Json(name = "use_header_anomaly_detection") + val useHeaderAnomalyDetection: Boolean? = null, + /** + * Sensitivity threshold for spam scoring (higher = more aggressive). + */ + @Json(name = "spam_sensitivity") + val spamSensitivity: Double? = null, +) diff --git a/src/main/kotlin/com/nylas/models/Rule.kt b/src/main/kotlin/com/nylas/models/Rule.kt new file mode 100644 index 00000000..447b483f --- /dev/null +++ b/src/main/kotlin/com/nylas/models/Rule.kt @@ -0,0 +1,69 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas rule object. + */ +data class Rule( + /** + * Globally unique object identifier. + */ + @Json(name = "id") + val id: String = "", + /** + * Name of the rule. + */ + @Json(name = "name") + val name: String = "", + /** + * Optional description of the rule. + */ + @Json(name = "description") + val description: String? = null, + /** + * Evaluation order — lower numbers run first. Range 0–1000, default 10. + */ + @Json(name = "priority") + val priority: Int = 10, + /** + * Whether the rule is active. + */ + @Json(name = "enabled") + val enabled: Boolean = true, + /** + * When this rule is evaluated — on inbound mail or outbound sends. + */ + @Json(name = "trigger") + val trigger: RuleTrigger = RuleTrigger.INBOUND, + /** + * The match conditions for this rule. + */ + @Json(name = "match") + val match: RuleMatch = RuleMatch(), + /** + * The actions to perform when conditions are met. + */ + @Json(name = "actions") + val actions: List = emptyList(), + /** + * The ID of the Nylas application this rule belongs to. + */ + @Json(name = "application_id") + val applicationId: String = "", + /** + * The ID of the organization this rule belongs to. + */ + @Json(name = "organization_id") + val organizationId: String = "", + /** + * Unix timestamp when the rule was created. + */ + @Json(name = "created_at") + val createdAt: Long = 0, + /** + * Unix timestamp when the rule was last updated. + */ + @Json(name = "updated_at") + val updatedAt: Long = 0, +) diff --git a/src/main/kotlin/com/nylas/models/RuleAction.kt b/src/main/kotlin/com/nylas/models/RuleAction.kt new file mode 100644 index 00000000..0525883b --- /dev/null +++ b/src/main/kotlin/com/nylas/models/RuleAction.kt @@ -0,0 +1,19 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of an action in a Nylas rule. + */ +data class RuleAction( + /** + * The action to perform. + */ + @Json(name = "type") + val type: RuleActionType = RuleActionType.BLOCK, + /** + * Required when type is [RuleActionType.ASSIGN_TO_FOLDER] — the folder ID to assign to. + */ + @Json(name = "value") + val value: String? = null, +) diff --git a/src/main/kotlin/com/nylas/models/RuleActionType.kt b/src/main/kotlin/com/nylas/models/RuleActionType.kt new file mode 100644 index 00000000..df443859 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/RuleActionType.kt @@ -0,0 +1,52 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * The action type for a Nylas rule. + * Note: [BLOCK] is terminal and cannot be combined with other actions. + */ +enum class RuleActionType { + /** + * Rejects the message at the SMTP level (inbound) or the send request with HTTP 403 (outbound). + * Terminal — cannot be combined with other actions. + */ + @Json(name = "block") + BLOCK, + + /** + * Delivers the message to the spam/junk folder. + */ + @Json(name = "mark_as_spam") + MARK_AS_SPAM, + + /** + * Moves the message to a folder. Requires [RuleAction.value] to be set to the folder ID. + */ + @Json(name = "assign_to_folder") + ASSIGN_TO_FOLDER, + + /** + * Marks the message as read. + */ + @Json(name = "mark_as_read") + MARK_AS_READ, + + /** + * Marks the message as starred/flagged. + */ + @Json(name = "mark_as_starred") + MARK_AS_STARRED, + + /** + * Moves the message to the archive folder. + */ + @Json(name = "archive") + ARCHIVE, + + /** + * Moves the message to the trash folder. + */ + @Json(name = "trash") + TRASH, +} diff --git a/src/main/kotlin/com/nylas/models/RuleCondition.kt b/src/main/kotlin/com/nylas/models/RuleCondition.kt new file mode 100644 index 00000000..cfbb65f8 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/RuleCondition.kt @@ -0,0 +1,26 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a single condition in a Nylas rule match. + */ +data class RuleCondition( + /** + * The field to evaluate. + * Inbound: from.address, from.domain, from.tld. + * Outbound: recipient.address, recipient.domain, recipient.tld, outbound.type. + */ + @Json(name = "field") + val field: String = "", + /** + * The comparison operator. + */ + @Json(name = "operator") + val operator: RuleConditionOperator = RuleConditionOperator.IS, + /** + * The value to compare against. For [RuleConditionOperator.IN_LIST], this is the List resource ID. + */ + @Json(name = "value") + val value: String = "", +) diff --git a/src/main/kotlin/com/nylas/models/RuleConditionOperator.kt b/src/main/kotlin/com/nylas/models/RuleConditionOperator.kt new file mode 100644 index 00000000..175fc537 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/RuleConditionOperator.kt @@ -0,0 +1,23 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * The comparison operator for a Nylas rule condition. + */ +enum class RuleConditionOperator { + @Json(name = "is") + IS, + + @Json(name = "is_not") + IS_NOT, + + @Json(name = "contains") + CONTAINS, + + /** + * Matches against all values in a referenced List resource. + */ + @Json(name = "in_list") + IN_LIST, +} diff --git a/src/main/kotlin/com/nylas/models/RuleMatch.kt b/src/main/kotlin/com/nylas/models/RuleMatch.kt new file mode 100644 index 00000000..59c3aac1 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/RuleMatch.kt @@ -0,0 +1,19 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of the match block in a Nylas rule. + */ +data class RuleMatch( + /** + * Logical operator used to combine conditions. + */ + @Json(name = "operator") + val operator: RuleMatchOperator = RuleMatchOperator.ANY, + /** + * The list of conditions to evaluate. + */ + @Json(name = "conditions") + val conditions: List = emptyList(), +) diff --git a/src/main/kotlin/com/nylas/models/RuleMatchOperator.kt b/src/main/kotlin/com/nylas/models/RuleMatchOperator.kt new file mode 100644 index 00000000..4855b7f3 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/RuleMatchOperator.kt @@ -0,0 +1,20 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * The logical operator used to combine conditions in a Nylas rule match. + */ +enum class RuleMatchOperator { + /** + * At least one condition must match. + */ + @Json(name = "any") + ANY, + + /** + * All conditions must match. + */ + @Json(name = "all") + ALL, +} diff --git a/src/main/kotlin/com/nylas/models/RuleTrigger.kt b/src/main/kotlin/com/nylas/models/RuleTrigger.kt new file mode 100644 index 00000000..96b852c7 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/RuleTrigger.kt @@ -0,0 +1,20 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * The trigger type for a Nylas rule. + */ +enum class RuleTrigger { + /** + * Rule is evaluated when mail arrives. + */ + @Json(name = "inbound") + INBOUND, + + /** + * Rule is evaluated before a send is submitted to the email provider. + */ + @Json(name = "outbound") + OUTBOUND, +} diff --git a/src/main/kotlin/com/nylas/models/UpdateNylasListRequest.kt b/src/main/kotlin/com/nylas/models/UpdateNylasListRequest.kt new file mode 100644 index 00000000..f0fd25d6 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/UpdateNylasListRequest.kt @@ -0,0 +1,48 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas update list request. + * Note: only [name] and [description] can be updated — [NylasList.type] is immutable. + */ +data class UpdateNylasListRequest( + /** + * Updated name of the list. + */ + @Json(name = "name") + val name: String? = null, + /** + * Updated description of the list. + */ + @Json(name = "description") + val description: String? = null, +) { + /** + * Builder for [UpdateNylasListRequest]. + */ + class Builder { + private var name: String? = null + private var description: String? = null + + /** + * Set the name of the list. + * @param name Updated name of the list. + * @return The builder. + */ + fun name(name: String) = apply { this.name = name } + + /** + * Set the description of the list. + * @param description Updated description of the list. + * @return The builder. + */ + fun description(description: String) = apply { this.description = description } + + /** + * Build the [UpdateNylasListRequest]. + * @return An [UpdateNylasListRequest] with the provided values. + */ + fun build() = UpdateNylasListRequest(name, description) + } +} diff --git a/src/main/kotlin/com/nylas/models/UpdatePolicyRequest.kt b/src/main/kotlin/com/nylas/models/UpdatePolicyRequest.kt new file mode 100644 index 00000000..1ae2181d --- /dev/null +++ b/src/main/kotlin/com/nylas/models/UpdatePolicyRequest.kt @@ -0,0 +1,86 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas update policy request. + */ +data class UpdatePolicyRequest( + /** + * Name of the policy. + */ + @Json(name = "name") + val name: String? = null, + /** + * Optional mailbox and behavior settings. + */ + @Json(name = "options") + val options: PolicyOptions? = null, + /** + * Resource and rate limits for agent accounts using this policy. + */ + @Json(name = "limits") + val limits: PolicyLimits? = null, + /** + * IDs of rules to link to this policy. + */ + @Json(name = "rules") + val rules: List? = null, + /** + * Spam detection configuration. + */ + @Json(name = "spam_detection") + val spamDetection: PolicySpamDetection? = null, +) { + /** + * Builder for [UpdatePolicyRequest]. + */ + class Builder { + private var name: String? = null + private var options: PolicyOptions? = null + private var limits: PolicyLimits? = null + private var rules: List? = null + private var spamDetection: PolicySpamDetection? = null + + /** + * Set the name of the policy. + * @param name Name of the policy. + * @return The builder. + */ + fun name(name: String) = apply { this.name = name } + + /** + * Set the mailbox and behavior settings. + * @param options Optional mailbox and behavior settings. + * @return The builder. + */ + fun options(options: PolicyOptions) = apply { this.options = options } + + /** + * Set the resource and rate limits. + * @param limits Resource and rate limits for agent accounts using this policy. + * @return The builder. + */ + fun limits(limits: PolicyLimits) = apply { this.limits = limits } + + /** + * Set the IDs of rules to link to this policy. + * @param rules IDs of rules to link to this policy. + * @return The builder. + */ + fun rules(rules: List) = apply { this.rules = rules } + + /** + * Set the spam detection configuration. + * @param spamDetection Spam detection configuration. + * @return The builder. + */ + fun spamDetection(spamDetection: PolicySpamDetection) = apply { this.spamDetection = spamDetection } + + /** + * Build the [UpdatePolicyRequest]. + * @return An [UpdatePolicyRequest] with the provided values. + */ + fun build() = UpdatePolicyRequest(name, options, limits, rules, spamDetection) + } +} diff --git a/src/main/kotlin/com/nylas/models/UpdateRuleRequest.kt b/src/main/kotlin/com/nylas/models/UpdateRuleRequest.kt new file mode 100644 index 00000000..afbf02d5 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/UpdateRuleRequest.kt @@ -0,0 +1,112 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas update rule request. + */ +data class UpdateRuleRequest( + /** + * Name of the rule. + */ + @Json(name = "name") + val name: String? = null, + /** + * When this rule is evaluated. + */ + @Json(name = "trigger") + val trigger: RuleTrigger? = null, + /** + * The match conditions for this rule. + */ + @Json(name = "match") + val match: RuleMatch? = null, + /** + * The actions to perform when conditions are met. + */ + @Json(name = "actions") + val actions: List? = null, + /** + * Optional description of the rule. + */ + @Json(name = "description") + val description: String? = null, + /** + * Evaluation order — lower numbers run first. Range 0–1000. + */ + @Json(name = "priority") + val priority: Int? = null, + /** + * Whether the rule is active. + */ + @Json(name = "enabled") + val enabled: Boolean? = null, +) { + /** + * Builder for [UpdateRuleRequest]. + */ + class Builder { + private var name: String? = null + private var trigger: RuleTrigger? = null + private var match: RuleMatch? = null + private var actions: List? = null + private var description: String? = null + private var priority: Int? = null + private var enabled: Boolean? = null + + /** + * Set the name of the rule. + * @param name Name of the rule. + * @return The builder. + */ + fun name(name: String) = apply { this.name = name } + + /** + * Set when the rule is evaluated. + * @param trigger When this rule is evaluated. + * @return The builder. + */ + fun trigger(trigger: RuleTrigger) = apply { this.trigger = trigger } + + /** + * Set the match conditions. + * @param match The match conditions for this rule. + * @return The builder. + */ + fun match(match: RuleMatch) = apply { this.match = match } + + /** + * Set the actions to perform. + * @param actions The actions to perform when conditions are met. + * @return The builder. + */ + fun actions(actions: List) = apply { this.actions = actions } + + /** + * Set the description of the rule. + * @param description Optional description of the rule. + * @return The builder. + */ + fun description(description: String) = apply { this.description = description } + + /** + * Set the evaluation priority. + * @param priority Evaluation order — lower numbers run first. Range 0–1000. + * @return The builder. + */ + fun priority(priority: Int) = apply { this.priority = priority } + + /** + * Set whether the rule is active. + * @param enabled Whether the rule is active. + * @return The builder. + */ + fun enabled(enabled: Boolean) = apply { this.enabled = enabled } + + /** + * Build the [UpdateRuleRequest]. + * @return An [UpdateRuleRequest] with the provided values. + */ + fun build() = UpdateRuleRequest(name, trigger, match, actions, description, priority, enabled) + } +} diff --git a/src/main/kotlin/com/nylas/resources/NylasLists.kt b/src/main/kotlin/com/nylas/resources/NylasLists.kt new file mode 100644 index 00000000..87c35f04 --- /dev/null +++ b/src/main/kotlin/com/nylas/resources/NylasLists.kt @@ -0,0 +1,142 @@ +package com.nylas.resources + +import com.nylas.NylasClient +import com.nylas.models.* +import com.nylas.util.JsonHelper +import com.squareup.moshi.Types + +/** + * Nylas Lists API + * + * The Nylas Lists API allows you to create and manage typed collections of domains, TLDs, + * or email addresses. Lists are referenced by rule conditions via the [RuleConditionOperator.IN_LIST] operator. + * + * @param client The configured Nylas API client + */ +class NylasLists(client: NylasClient) : Resource(client, NylasList::class.java) { + /** + * Return all lists for your application. + * @param queryParams Optional query parameters to apply + * @param overrides Optional request overrides to apply + * @return The list of Nylas lists + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun list(queryParams: ListNylasListsQueryParams? = null, overrides: RequestOverrides? = null): ListResponse { + return listResource("v3/lists", queryParams, overrides) + } + + /** + * Return a Nylas list. + * @param listId The ID of the list to retrieve + * @param overrides Optional request overrides to apply + * @return The Nylas list + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun find(listId: String, overrides: RequestOverrides? = null): Response { + val path = String.format("v3/lists/%s", listId) + return findResource(path, overrides = overrides) + } + + /** + * Create a Nylas list. + * @param requestBody The values to create the list with + * @param overrides Optional request overrides to apply + * @return The created Nylas list + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun create(requestBody: CreateNylasListRequest, overrides: RequestOverrides? = null): Response { + val serializedRequestBody = JsonHelper.moshi().adapter(CreateNylasListRequest::class.java).toJson(requestBody) + return createResource("v3/lists", serializedRequestBody, overrides = overrides) + } + + /** + * Update a Nylas list. Only [UpdateNylasListRequest.name] and [UpdateNylasListRequest.description] + * can be changed — the list type is immutable after creation. + * @param listId The ID of the list to update + * @param requestBody The values to update the list with + * @param overrides Optional request overrides to apply + * @return The updated Nylas list + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun update(listId: String, requestBody: UpdateNylasListRequest, overrides: RequestOverrides? = null): Response { + val path = String.format("v3/lists/%s", listId) + val serializedRequestBody = JsonHelper.moshi().adapter(UpdateNylasListRequest::class.java).toJson(requestBody) + return updateResource(path, serializedRequestBody, overrides = overrides) + } + + /** + * Delete a Nylas list. Cascades to all items in the list. + * @param listId The ID of the list to delete + * @param overrides Optional request overrides to apply + * @return The deletion response + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun destroy(listId: String, overrides: RequestOverrides? = null): DeleteResponse { + val path = String.format("v3/lists/%s", listId) + return destroyResource(path, overrides = overrides) + } + + /** + * Return all items in a Nylas list. + * @param listId The ID of the list + * @param queryParams Optional query parameters to apply + * @param overrides Optional request overrides to apply + * @return The paginated list of items + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun listItems( + listId: String, + queryParams: ListNylasListItemsQueryParams? = null, + overrides: RequestOverrides? = null, + ): ListResponse { + val path = String.format("v3/lists/%s/items", listId) + val responseType = Types.newParameterizedType(ListResponse::class.java, NylasListItem::class.java) + return client.executeGet(path, responseType, queryParams, overrides) + } + + /** + * Add items to a Nylas list. Duplicate additions are silently ignored. Max 1000 items per request. + * @param listId The ID of the list + * @param requestBody The items to add + * @param overrides Optional request overrides to apply + * @return The updated Nylas list + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun addItems( + listId: String, + requestBody: ListItemsRequest, + overrides: RequestOverrides? = null, + ): Response { + val path = String.format("v3/lists/%s/items", listId) + val serializedRequestBody = JsonHelper.moshi().adapter(ListItemsRequest::class.java).toJson(requestBody) + val responseType = Types.newParameterizedType(Response::class.java, NylasList::class.java) + return client.executePost(path, responseType, serializedRequestBody, overrides = overrides) + } + + /** + * Remove items from a Nylas list. Values not in the list are silently ignored. Max 1000 items per request. + * @param listId The ID of the list + * @param requestBody The items to remove + * @param overrides Optional request overrides to apply + * @return The updated Nylas list + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun removeItems( + listId: String, + requestBody: ListItemsRequest, + overrides: RequestOverrides? = null, + ): Response { + val path = String.format("v3/lists/%s/items", listId) + val serializedRequestBody = JsonHelper.moshi().adapter(ListItemsRequest::class.java).toJson(requestBody) + val responseType = Types.newParameterizedType(Response::class.java, NylasList::class.java) + return client.executeDelete(path, responseType, serializedRequestBody, overrides = overrides) + } +} diff --git a/src/main/kotlin/com/nylas/resources/Policies.kt b/src/main/kotlin/com/nylas/resources/Policies.kt new file mode 100644 index 00000000..101db697 --- /dev/null +++ b/src/main/kotlin/com/nylas/resources/Policies.kt @@ -0,0 +1,81 @@ +package com.nylas.resources + +import com.nylas.NylasClient +import com.nylas.models.* +import com.nylas.util.JsonHelper + +/** + * Nylas Policies API + * + * The Nylas Policies API allows you to create and manage policies for Nylas Agent Accounts. + * Policies define message limits, spam detection, retention, and linked rules. + * + * @param client The configured Nylas API client + */ +class Policies(client: NylasClient) : Resource(client, Policy::class.java) { + /** + * Return all policies for your application. + * @param queryParams Optional query parameters to apply + * @param overrides Optional request overrides to apply + * @return The list of policies + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun list(queryParams: ListPoliciesQueryParams? = null, overrides: RequestOverrides? = null): ListResponse { + return listResource("v3/policies", queryParams, overrides) + } + + /** + * Return a policy. + * @param policyId The ID of the policy to retrieve + * @param overrides Optional request overrides to apply + * @return The policy + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun find(policyId: String, overrides: RequestOverrides? = null): Response { + val path = String.format("v3/policies/%s", policyId) + return findResource(path, overrides = overrides) + } + + /** + * Create a policy. + * @param requestBody The values to create the policy with + * @param overrides Optional request overrides to apply + * @return The created policy + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun create(requestBody: CreatePolicyRequest, overrides: RequestOverrides? = null): Response { + val serializedRequestBody = JsonHelper.moshi().adapter(CreatePolicyRequest::class.java).toJson(requestBody) + return createResource("v3/policies", serializedRequestBody, overrides = overrides) + } + + /** + * Update a policy. + * @param policyId The ID of the policy to update + * @param requestBody The values to update the policy with + * @param overrides Optional request overrides to apply + * @return The updated policy + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun update(policyId: String, requestBody: UpdatePolicyRequest, overrides: RequestOverrides? = null): Response { + val path = String.format("v3/policies/%s", policyId) + val serializedRequestBody = JsonHelper.moshi().adapter(UpdatePolicyRequest::class.java).toJson(requestBody) + return updateResource(path, serializedRequestBody, overrides = overrides) + } + + /** + * Delete a policy. + * @param policyId The ID of the policy to delete + * @param overrides Optional request overrides to apply + * @return The deletion response + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun destroy(policyId: String, overrides: RequestOverrides? = null): DeleteResponse { + val path = String.format("v3/policies/%s", policyId) + return destroyResource(path, overrides = overrides) + } +} diff --git a/src/main/kotlin/com/nylas/resources/Rules.kt b/src/main/kotlin/com/nylas/resources/Rules.kt new file mode 100644 index 00000000..91a9dd89 --- /dev/null +++ b/src/main/kotlin/com/nylas/resources/Rules.kt @@ -0,0 +1,81 @@ +package com.nylas.resources + +import com.nylas.NylasClient +import com.nylas.models.* +import com.nylas.util.JsonHelper + +/** + * Nylas Rules API + * + * The Nylas Rules API allows you to create and manage rules for Nylas Agent Accounts. + * Rules filter inbound mail or restrict outbound sends based on conditions and actions. + * + * @param client The configured Nylas API client + */ +class Rules(client: NylasClient) : Resource(client, Rule::class.java) { + /** + * Return all rules for your application. + * @param queryParams Optional query parameters to apply + * @param overrides Optional request overrides to apply + * @return The list of rules + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun list(queryParams: ListRulesQueryParams? = null, overrides: RequestOverrides? = null): ListResponse { + return listResource("v3/rules", queryParams, overrides) + } + + /** + * Return a rule. + * @param ruleId The ID of the rule to retrieve + * @param overrides Optional request overrides to apply + * @return The rule + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun find(ruleId: String, overrides: RequestOverrides? = null): Response { + val path = String.format("v3/rules/%s", ruleId) + return findResource(path, overrides = overrides) + } + + /** + * Create a rule. + * @param requestBody The values to create the rule with + * @param overrides Optional request overrides to apply + * @return The created rule + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun create(requestBody: CreateRuleRequest, overrides: RequestOverrides? = null): Response { + val serializedRequestBody = JsonHelper.moshi().adapter(CreateRuleRequest::class.java).toJson(requestBody) + return createResource("v3/rules", serializedRequestBody, overrides = overrides) + } + + /** + * Update a rule. + * @param ruleId The ID of the rule to update + * @param requestBody The values to update the rule with + * @param overrides Optional request overrides to apply + * @return The updated rule + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun update(ruleId: String, requestBody: UpdateRuleRequest, overrides: RequestOverrides? = null): Response { + val path = String.format("v3/rules/%s", ruleId) + val serializedRequestBody = JsonHelper.moshi().adapter(UpdateRuleRequest::class.java).toJson(requestBody) + return updateResource(path, serializedRequestBody, overrides = overrides) + } + + /** + * Delete a rule. + * @param ruleId The ID of the rule to delete + * @param overrides Optional request overrides to apply + * @return The deletion response + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + @JvmOverloads + fun destroy(ruleId: String, overrides: RequestOverrides? = null): DeleteResponse { + val path = String.format("v3/rules/%s", ruleId) + return destroyResource(path, overrides = overrides) + } +} diff --git a/src/test/kotlin/com/nylas/resources/NylasListsTests.kt b/src/test/kotlin/com/nylas/resources/NylasListsTests.kt new file mode 100644 index 00000000..c2e39adc --- /dev/null +++ b/src/test/kotlin/com/nylas/resources/NylasListsTests.kt @@ -0,0 +1,326 @@ +package com.nylas.resources + +import com.nylas.NylasClient +import com.nylas.models.* +import com.nylas.models.Response +import com.nylas.util.JsonHelper +import com.squareup.moshi.Types +import okhttp3.Call +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import okio.Buffer +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import java.lang.reflect.Type +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull + +class NylasListsTests { + private val mockHttpClient: OkHttpClient = Mockito.mock(OkHttpClient::class.java) + private val mockCall: Call = Mockito.mock(Call::class.java) + private val mockResponse: okhttp3.Response = Mockito.mock(okhttp3.Response::class.java) + private val mockResponseBody: ResponseBody = Mockito.mock(ResponseBody::class.java) + private val mockOkHttpClientBuilder: OkHttpClient.Builder = Mockito.mock() + + @BeforeEach + fun setup() { + MockitoAnnotations.openMocks(this) + whenever(mockOkHttpClientBuilder.addInterceptor(any())).thenReturn(mockOkHttpClientBuilder) + whenever(mockOkHttpClientBuilder.build()).thenReturn(mockHttpClient) + whenever(mockHttpClient.newCall(any())).thenReturn(mockCall) + whenever(mockCall.execute()).thenReturn(mockResponse) + whenever(mockResponse.isSuccessful).thenReturn(true) + whenever(mockResponse.body).thenReturn(mockResponseBody) + } + + @Nested + inner class SerializationTests { + @Test + fun `NylasList deserializes properly`() { + val adapter = JsonHelper.moshi().adapter(NylasList::class.java) + val jsonBuffer = Buffer().writeUtf8( + """ + { + "id": "d1e2f3a4-5678-4abc-9def-0123456789ab", + "name": "Blocked domains", + "description": "Domains sending unwanted mail.", + "type": "domain", + "items_count": 42, + "application_id": "ad410018-d306-43f9-8361-fa5d7b2172e0", + "organization_id": "org-abc123", + "created_at": 1742932766, + "updated_at": 1742932766 + } + """.trimIndent(), + ) + + val list = adapter.fromJson(jsonBuffer)!! + assertIs(list) + assertEquals("d1e2f3a4-5678-4abc-9def-0123456789ab", list.id) + assertEquals("Blocked domains", list.name) + assertEquals("Domains sending unwanted mail.", list.description) + assertEquals(NylasListType.DOMAIN, list.type) + assertEquals(42, list.itemsCount) + assertEquals("ad410018-d306-43f9-8361-fa5d7b2172e0", list.applicationId) + assertEquals("org-abc123", list.organizationId) + assertEquals(1742932766L, list.createdAt) + assertEquals(1742932766L, list.updatedAt) + } + + @Test + fun `NylasListItem deserializes properly`() { + val adapter = JsonHelper.moshi().adapter(NylasListItem::class.java) + val jsonBuffer = Buffer().writeUtf8( + """ + { + "id": "e1f2a3b4-5678-4abc-9def-0123456789ab", + "list_id": "d1e2f3a4-5678-4abc-9def-0123456789ab", + "value": "spam-domain.com", + "created_at": 1742932766 + } + """.trimIndent(), + ) + + val item = adapter.fromJson(jsonBuffer)!! + assertIs(item) + assertEquals("e1f2a3b4-5678-4abc-9def-0123456789ab", item.id) + assertEquals("d1e2f3a4-5678-4abc-9def-0123456789ab", item.listId) + assertEquals("spam-domain.com", item.value) + assertEquals(1742932766L, item.createdAt) + } + + @Test + fun `CreateNylasListRequest serializes correctly`() { + val adapter = JsonHelper.moshi().adapter(CreateNylasListRequest::class.java) + val request = CreateNylasListRequest(name = "Blocked domains", type = NylasListType.DOMAIN) + val json = adapter.toJson(request) + val deserialized = adapter.fromJson(json)!! + assertEquals("Blocked domains", deserialized.name) + assertEquals(NylasListType.DOMAIN, deserialized.type) + assertNull(deserialized.description) + } + + @Test + fun `CreateNylasListRequest Builder sets all fields`() { + val request = CreateNylasListRequest.Builder("My TLD List", NylasListType.TLD) + .description("Top-level domains to block") + .build() + + assertEquals("My TLD List", request.name) + assertEquals(NylasListType.TLD, request.type) + assertEquals("Top-level domains to block", request.description) + } + + @Test + fun `ListItemsRequest serializes correctly`() { + val adapter = JsonHelper.moshi().adapter(ListItemsRequest::class.java) + val request = ListItemsRequest(items = listOf("spam.com", "bad.net")) + val json = adapter.toJson(request) + val deserialized = adapter.fromJson(json)!! + assertEquals(listOf("spam.com", "bad.net"), deserialized.items) + } + } + + @Nested + inner class CrudTests { + private lateinit var mockNylasClient: NylasClient + private lateinit var nylaslists: NylasLists + + @BeforeEach + fun setup() { + mockNylasClient = Mockito.mock(NylasClient::class.java) + nylaslists = NylasLists(mockNylasClient) + } + + @Test + fun `listing lists calls requests with the correct params`() { + nylaslists.list() + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(ListResponse::class.java, NylasList::class.java), typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `finding a list calls requests with the correct params`() { + val listId = "list-abc123" + nylaslists.find(listId) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists/$listId", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, NylasList::class.java), typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `creating a list calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(CreateNylasListRequest::class.java) + val requestBody = CreateNylasListRequest(name = "Blocked domains", type = NylasListType.DOMAIN) + nylaslists.create(requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePost>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, NylasList::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + + @Test + fun `updating a list calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(UpdateNylasListRequest::class.java) + val listId = "list-abc123" + val requestBody = UpdateNylasListRequest(name = "Updated name") + nylaslists.update(listId, requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePut>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists/$listId", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, NylasList::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + + @Test + fun `destroying a list calls requests with the correct params`() { + val listId = "list-abc123" + nylaslists.destroy(listId) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeDelete( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists/$listId", pathCaptor.firstValue) + assertEquals(DeleteResponse::class.java, typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `listing items in a list calls requests with the correct params`() { + val listId = "list-abc123" + nylaslists.listItems(listId) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists/$listId/items", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(ListResponse::class.java, NylasListItem::class.java), typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `adding items to a list calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(ListItemsRequest::class.java) + val listId = "list-abc123" + val requestBody = ListItemsRequest(items = listOf("spam.com", "bad.net")) + nylaslists.addItems(listId, requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePost>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists/$listId/items", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, NylasList::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + + @Test + fun `removing items from a list calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(ListItemsRequest::class.java) + val listId = "list-abc123" + val requestBody = ListItemsRequest(items = listOf("spam.com")) + nylaslists.removeItems(listId, requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeDelete>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/lists/$listId/items", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, NylasList::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + } +} diff --git a/src/test/kotlin/com/nylas/resources/PoliciesTests.kt b/src/test/kotlin/com/nylas/resources/PoliciesTests.kt new file mode 100644 index 00000000..7ad2b4fc --- /dev/null +++ b/src/test/kotlin/com/nylas/resources/PoliciesTests.kt @@ -0,0 +1,304 @@ +package com.nylas.resources + +import com.nylas.NylasClient +import com.nylas.models.* +import com.nylas.models.Response +import com.nylas.util.JsonHelper +import com.squareup.moshi.Types +import okhttp3.Call +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import okio.Buffer +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import java.lang.reflect.Type +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull + +class PoliciesTests { + private val mockHttpClient: OkHttpClient = Mockito.mock(OkHttpClient::class.java) + private val mockCall: Call = Mockito.mock(Call::class.java) + private val mockResponse: okhttp3.Response = Mockito.mock(okhttp3.Response::class.java) + private val mockResponseBody: ResponseBody = Mockito.mock(ResponseBody::class.java) + private val mockOkHttpClientBuilder: OkHttpClient.Builder = Mockito.mock() + + @BeforeEach + fun setup() { + MockitoAnnotations.openMocks(this) + whenever(mockOkHttpClientBuilder.addInterceptor(any())).thenReturn(mockOkHttpClientBuilder) + whenever(mockOkHttpClientBuilder.build()).thenReturn(mockHttpClient) + whenever(mockHttpClient.newCall(any())).thenReturn(mockCall) + whenever(mockCall.execute()).thenReturn(mockResponse) + whenever(mockResponse.isSuccessful).thenReturn(true) + whenever(mockResponse.body).thenReturn(mockResponseBody) + } + + @Nested + inner class SerializationTests { + @Test + fun `Policy deserializes properly`() { + val adapter = JsonHelper.moshi().adapter(Policy::class.java) + val jsonBuffer = Buffer().writeUtf8( + """ + { + "id": "b1c2d3e4-5678-4abc-9def-0123456789ab", + "name": "Standard Agent Account Policy", + "application_id": "ad410018-d306-43f9-8361-fa5d7b2172e0", + "organization_id": "org-abc123", + "options": { + "additional_folders": ["archive", "follow-up"], + "use_cidr_aliasing": false + }, + "limits": { + "limit_attachment_size_limit": 26214400, + "limit_attachment_count_limit": 50, + "limit_attachment_allowed_types": ["image/png", "application/pdf"], + "limit_size_total_mime": 31457280, + "limit_storage_total": 10737418240, + "limit_count_daily_message_per_grant": 1000, + "limit_inbox_retention_period": 365, + "limit_spam_retention_period": 30 + }, + "rules": ["c1d2e3f4-5678-4abc-9def-0123456789ab"], + "spam_detection": { + "use_list_dnsbl": true, + "use_header_anomaly_detection": true, + "spam_sensitivity": 1.5 + }, + "created_at": 1742932766, + "updated_at": 1742932766 + } + """.trimIndent(), + ) + + val policy = adapter.fromJson(jsonBuffer)!! + assertIs(policy) + assertEquals("b1c2d3e4-5678-4abc-9def-0123456789ab", policy.id) + assertEquals("Standard Agent Account Policy", policy.name) + assertEquals("ad410018-d306-43f9-8361-fa5d7b2172e0", policy.applicationId) + assertEquals("org-abc123", policy.organizationId) + assertEquals(listOf("archive", "follow-up"), policy.options?.additionalFolders) + assertEquals(false, policy.options?.useCidrAliasing) + assertEquals(26214400L, policy.limits?.attachmentSizeLimit) + assertEquals(50, policy.limits?.attachmentCountLimit) + assertEquals(listOf("image/png", "application/pdf"), policy.limits?.attachmentAllowedTypes) + assertEquals(1000, policy.limits?.countDailyMessagePerGrant) + assertEquals(365, policy.limits?.inboxRetentionPeriod) + assertEquals(30, policy.limits?.spamRetentionPeriod) + assertEquals(listOf("c1d2e3f4-5678-4abc-9def-0123456789ab"), policy.rules) + assertEquals(true, policy.spamDetection?.useListDnsbl) + assertEquals(true, policy.spamDetection?.useHeaderAnomalyDetection) + assertEquals(1.5, policy.spamDetection?.spamSensitivity) + assertEquals(1742932766L, policy.createdAt) + assertEquals(1742932766L, policy.updatedAt) + } + + @Test + fun `Policy with null optional fields deserializes properly`() { + val adapter = JsonHelper.moshi().adapter(Policy::class.java) + val jsonBuffer = Buffer().writeUtf8( + """ + { + "id": "b1c2d3e4-5678-4abc-9def-0123456789ab", + "name": "Minimal Policy", + "application_id": "ad410018-d306-43f9-8361-fa5d7b2172e0", + "organization_id": "org-abc123", + "created_at": 1742932766, + "updated_at": 1742932766 + } + """.trimIndent(), + ) + + val policy = adapter.fromJson(jsonBuffer)!! + assertIs(policy) + assertNull(policy.options) + assertNull(policy.limits) + assertNull(policy.rules) + assertNull(policy.spamDetection) + } + + @Test + fun `CreatePolicyRequest serializes with only required fields`() { + val adapter = JsonHelper.moshi().adapter(CreatePolicyRequest::class.java) + val request = CreatePolicyRequest(name = "My Policy") + val json = adapter.toJson(request) + val deserialized = adapter.fromJson(json)!! + assertEquals("My Policy", deserialized.name) + assertNull(deserialized.options) + assertNull(deserialized.limits) + assertNull(deserialized.rules) + assertNull(deserialized.spamDetection) + } + + @Test + fun `CreatePolicyRequest serializes all fields`() { + val adapter = JsonHelper.moshi().adapter(CreatePolicyRequest::class.java) + val request = CreatePolicyRequest.Builder("Full Policy") + .options(PolicyOptions(additionalFolders = listOf("archive"), useCidrAliasing = true)) + .limits(PolicyLimits(countDailyMessagePerGrant = 500)) + .rules(listOf("rule-id-1")) + .spamDetection(PolicySpamDetection(spamSensitivity = 2.0)) + .build() + + val json = adapter.toJson(request) + val deserialized = adapter.fromJson(json)!! + assertEquals("Full Policy", deserialized.name) + assertEquals(listOf("archive"), deserialized.options?.additionalFolders) + assertEquals(500, deserialized.limits?.countDailyMessagePerGrant) + assertEquals(listOf("rule-id-1"), deserialized.rules) + assertEquals(2.0, deserialized.spamDetection?.spamSensitivity) + } + } + + @Nested + inner class CrudTests { + private lateinit var mockNylasClient: NylasClient + private lateinit var policies: Policies + + @BeforeEach + fun setup() { + mockNylasClient = Mockito.mock(NylasClient::class.java) + policies = Policies(mockNylasClient) + } + + @Test + fun `listing policies calls requests with the correct params`() { + policies.list() + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/policies", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(ListResponse::class.java, Policy::class.java), typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `listing policies with query params passes them correctly`() { + val queryParams = ListPoliciesQueryParams(limit = 5, pageToken = "cursor123") + policies.list(queryParams) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/policies", pathCaptor.firstValue) + assertEquals(queryParams, queryParamCaptor.firstValue) + } + + @Test + fun `finding a policy calls requests with the correct params`() { + val policyId = "policy-abc123" + policies.find(policyId) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/policies/$policyId", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, Policy::class.java), typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `creating a policy calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(CreatePolicyRequest::class.java) + val requestBody = CreatePolicyRequest(name = "My Policy") + policies.create(requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePost>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/policies", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, Policy::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + + @Test + fun `updating a policy calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(UpdatePolicyRequest::class.java) + val policyId = "policy-abc123" + val requestBody = UpdatePolicyRequest(name = "Updated Policy") + policies.update(policyId, requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePut>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/policies/$policyId", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, Policy::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + + @Test + fun `destroying a policy calls requests with the correct params`() { + val policyId = "policy-abc123" + policies.destroy(policyId) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeDelete( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/policies/$policyId", pathCaptor.firstValue) + assertEquals(DeleteResponse::class.java, typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + } +} diff --git a/src/test/kotlin/com/nylas/resources/RulesTests.kt b/src/test/kotlin/com/nylas/resources/RulesTests.kt new file mode 100644 index 00000000..d47a273a --- /dev/null +++ b/src/test/kotlin/com/nylas/resources/RulesTests.kt @@ -0,0 +1,326 @@ +package com.nylas.resources + +import com.nylas.NylasClient +import com.nylas.models.* +import com.nylas.models.Response +import com.nylas.util.JsonHelper +import com.squareup.moshi.Types +import okhttp3.Call +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import okio.Buffer +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import java.lang.reflect.Type +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull + +class RulesTests { + private val mockHttpClient: OkHttpClient = Mockito.mock(OkHttpClient::class.java) + private val mockCall: Call = Mockito.mock(Call::class.java) + private val mockResponse: okhttp3.Response = Mockito.mock(okhttp3.Response::class.java) + private val mockResponseBody: ResponseBody = Mockito.mock(ResponseBody::class.java) + private val mockOkHttpClientBuilder: OkHttpClient.Builder = Mockito.mock() + + @BeforeEach + fun setup() { + MockitoAnnotations.openMocks(this) + whenever(mockOkHttpClientBuilder.addInterceptor(any())).thenReturn(mockOkHttpClientBuilder) + whenever(mockOkHttpClientBuilder.build()).thenReturn(mockHttpClient) + whenever(mockHttpClient.newCall(any())).thenReturn(mockCall) + whenever(mockCall.execute()).thenReturn(mockResponse) + whenever(mockResponse.isSuccessful).thenReturn(true) + whenever(mockResponse.body).thenReturn(mockResponseBody) + } + + @Nested + inner class SerializationTests { + @Test + fun `Rule deserializes properly`() { + val adapter = JsonHelper.moshi().adapter(Rule::class.java) + val jsonBuffer = Buffer().writeUtf8( + """ + { + "id": "c1d2e3f4-5678-4abc-9def-0123456789ab", + "name": "Block spam domains", + "description": "Rejects messages from known spam domains.", + "priority": 1, + "enabled": true, + "trigger": "inbound", + "match": { + "operator": "any", + "conditions": [ + { + "field": "from.domain", + "operator": "is", + "value": "spam-domain.com" + }, + { + "field": "from.tld", + "operator": "is", + "value": "xyz" + } + ] + }, + "actions": [ + { + "type": "block" + } + ], + "application_id": "ad410018-d306-43f9-8361-fa5d7b2172e0", + "organization_id": "org-abc123", + "created_at": 1742932766, + "updated_at": 1742932766 + } + """.trimIndent(), + ) + + val rule = adapter.fromJson(jsonBuffer)!! + assertIs(rule) + assertEquals("c1d2e3f4-5678-4abc-9def-0123456789ab", rule.id) + assertEquals("Block spam domains", rule.name) + assertEquals("Rejects messages from known spam domains.", rule.description) + assertEquals(1, rule.priority) + assertEquals(true, rule.enabled) + assertEquals(RuleTrigger.INBOUND, rule.trigger) + assertEquals(RuleMatchOperator.ANY, rule.match.operator) + assertEquals(2, rule.match.conditions.size) + assertEquals("from.domain", rule.match.conditions[0].field) + assertEquals(RuleConditionOperator.IS, rule.match.conditions[0].operator) + assertEquals("spam-domain.com", rule.match.conditions[0].value) + assertEquals(1, rule.actions.size) + assertEquals(RuleActionType.BLOCK, rule.actions[0].type) + assertNull(rule.actions[0].value) + assertEquals("ad410018-d306-43f9-8361-fa5d7b2172e0", rule.applicationId) + assertEquals(1742932766L, rule.createdAt) + } + + @Test + fun `Outbound rule with assign_to_folder action deserializes properly`() { + val adapter = JsonHelper.moshi().adapter(Rule::class.java) + val jsonBuffer = Buffer().writeUtf8( + """ + { + "id": "e3f4a5b6-789a-4cde-bf01-23456789abcd", + "name": "Archive sent replies", + "priority": 50, + "enabled": true, + "trigger": "outbound", + "match": { + "operator": "all", + "conditions": [ + { + "field": "outbound.type", + "operator": "is", + "value": "reply" + } + ] + }, + "actions": [ + { + "type": "assign_to_folder", + "value": "Label_1234567890" + } + ], + "application_id": "ad410018-d306-43f9-8361-fa5d7b2172e0", + "organization_id": "org-abc123", + "created_at": 1742933005, + "updated_at": 1742933005 + } + """.trimIndent(), + ) + + val rule = adapter.fromJson(jsonBuffer)!! + assertIs(rule) + assertEquals(RuleTrigger.OUTBOUND, rule.trigger) + assertEquals(RuleMatchOperator.ALL, rule.match.operator) + assertEquals("outbound.type", rule.match.conditions[0].field) + assertEquals(RuleActionType.ASSIGN_TO_FOLDER, rule.actions[0].type) + assertEquals("Label_1234567890", rule.actions[0].value) + } + + @Test + fun `CreateRuleRequest serializes with all required fields`() { + val adapter = JsonHelper.moshi().adapter(CreateRuleRequest::class.java) + val request = CreateRuleRequest( + name = "Block spam", + trigger = RuleTrigger.INBOUND, + match = RuleMatch( + operator = RuleMatchOperator.ANY, + conditions = listOf(RuleCondition(field = "from.domain", operator = RuleConditionOperator.IS, value = "spam.com")), + ), + actions = listOf(RuleAction(type = RuleActionType.BLOCK)), + ) + + val json = adapter.toJson(request) + val deserialized = adapter.fromJson(json)!! + assertEquals("Block spam", deserialized.name) + assertEquals(RuleTrigger.INBOUND, deserialized.trigger) + assertEquals(RuleMatchOperator.ANY, deserialized.match.operator) + assertEquals(1, deserialized.match.conditions.size) + assertEquals(RuleActionType.BLOCK, deserialized.actions[0].type) + assertNull(deserialized.description) + assertNull(deserialized.priority) + assertNull(deserialized.enabled) + } + + @Test + fun `CreateRuleRequest Builder sets all fields`() { + val request = CreateRuleRequest.Builder( + name = "My Rule", + trigger = RuleTrigger.OUTBOUND, + match = RuleMatch(operator = RuleMatchOperator.ALL, conditions = emptyList()), + actions = listOf(RuleAction(type = RuleActionType.ARCHIVE)), + ) + .description("Test rule") + .priority(5) + .enabled(false) + .build() + + assertEquals("My Rule", request.name) + assertEquals("Test rule", request.description) + assertEquals(5, request.priority) + assertEquals(false, request.enabled) + } + } + + @Nested + inner class CrudTests { + private lateinit var mockNylasClient: NylasClient + private lateinit var rules: Rules + + @BeforeEach + fun setup() { + mockNylasClient = Mockito.mock(NylasClient::class.java) + rules = Rules(mockNylasClient) + } + + @Test + fun `listing rules calls requests with the correct params`() { + rules.list() + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/rules", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(ListResponse::class.java, Rule::class.java), typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `finding a rule calls requests with the correct params`() { + val ruleId = "rule-abc123" + rules.find(ruleId) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeGet>( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/rules/$ruleId", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, Rule::class.java), typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + + @Test + fun `creating a rule calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(CreateRuleRequest::class.java) + val requestBody = CreateRuleRequest( + name = "Block spam", + trigger = RuleTrigger.INBOUND, + match = RuleMatch( + operator = RuleMatchOperator.ANY, + conditions = listOf(RuleCondition(field = "from.domain", operator = RuleConditionOperator.IS, value = "spam.com")), + ), + actions = listOf(RuleAction(type = RuleActionType.BLOCK)), + ) + rules.create(requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePost>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/rules", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, Rule::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + + @Test + fun `updating a rule calls requests with the correct params`() { + val adapter = JsonHelper.moshi().adapter(UpdateRuleRequest::class.java) + val ruleId = "rule-abc123" + val requestBody = UpdateRuleRequest(name = "Updated Rule", priority = 5) + rules.update(ruleId, requestBody) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePut>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/rules/$ruleId", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(Response::class.java, Rule::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(requestBody), requestBodyCaptor.firstValue) + } + + @Test + fun `destroying a rule calls requests with the correct params`() { + val ruleId = "rule-abc123" + rules.destroy(ruleId) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executeDelete( + pathCaptor.capture(), + typeCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/rules/$ruleId", pathCaptor.firstValue) + assertEquals(DeleteResponse::class.java, typeCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + } +}