Skip to content

Commit 1982da7

Browse files
CopilotRello
andauthored
feat(lock): add NKLock etag parsing
Co-authored-by: Rello <13385119+Rello@users.noreply.github.com>
1 parent d192518 commit 1982da7

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

‎Sources/NextcloudKit/Models/NKLock.swift‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public struct NKLock: Equatable, Sendable {
4747
///
4848
public let token: String?
4949

50+
///
51+
/// Normalized resource ETag returned by the LOCK response.
52+
///
53+
public let etag: String?
54+
5055
///
5156
/// Initialize from a SwiftyXML accessor.
5257
///
@@ -90,6 +95,8 @@ public struct NKLock: Equatable, Sendable {
9095
self.time = Date(timeIntervalSince1970: rawTime)
9196
self.timeOut = Date(timeIntervalSince1970: rawTime + rawTimeOut)
9297
self.token = lockToken
98+
self.etag = properties["d:getetag"].text?
99+
.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
93100
}
94101

95102
///
@@ -103,13 +110,14 @@ public struct NKLock: Equatable, Sendable {
103110
///
104111
/// Initialize from raw values.
105112
///
106-
public init(owner: String, ownerEditor: String, ownerType: NKLockType, ownerDisplayName: String, time: Date?, timeOut: Date?, token: String?) {
113+
public init(owner: String, ownerEditor: String, ownerType: NKLockType, ownerDisplayName: String, time: Date?, timeOut: Date?, token: String?, etag: String? = nil) {
107114
self.owner = owner
108115
self.ownerEditor = ownerEditor
109116
self.ownerType = ownerType
110117
self.ownerDisplayName = ownerDisplayName
111118
self.time = time
112119
self.timeOut = timeOut
113120
self.token = token
121+
self.etag = etag
114122
}
115123
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
2+
// SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
import Foundation
5+
import Testing
6+
@testable import NextcloudKit
7+
8+
@Suite("NKLock parsing")
9+
struct NKLockUnitTests {
10+
private func makeLockData(etagElement: String = "") -> Data {
11+
let xml = """
12+
<?xml version="1.0"?>
13+
<d:prop xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
14+
<nc:lock>1</nc:lock>
15+
<nc:lock-owner>user-id</nc:lock-owner>
16+
<nc:lock-owner-editor>text</nc:lock-owner-editor>
17+
<nc:lock-owner-type>\(NKLockType.token.rawValue)</nc:lock-owner-type>
18+
<nc:lock-owner-displayname>User Name</nc:lock-owner-displayname>
19+
<nc:lock-time>1</nc:lock-time>
20+
<nc:lock-timeout>60</nc:lock-timeout>
21+
<nc:lock-token>files_lock/test-token</nc:lock-token>
22+
\(etagElement)
23+
</d:prop>
24+
"""
25+
26+
return Data(xml.utf8)
27+
}
28+
29+
@Test("Parses quoted ETag from LOCK response")
30+
func parsesQuotedETag() {
31+
let lock = NKLock(data: makeLockData(etagElement: "<d:getetag>\"etag-after-lock\"</d:getetag>"))
32+
33+
#expect(lock != nil)
34+
#expect(lock?.token == "files_lock/test-token")
35+
#expect(lock?.etag == "etag-after-lock")
36+
}
37+
38+
@Test("Missing ETag keeps lock parsing valid")
39+
func missingETagDoesNotFailParsing() {
40+
let lock = NKLock(data: makeLockData())
41+
42+
#expect(lock != nil)
43+
#expect(lock?.etag == nil)
44+
}
45+
46+
@Test("Unquoted ETag is preserved")
47+
func preservesUnquotedETag() {
48+
let lock = NKLock(data: makeLockData(etagElement: "<d:getetag>etag-after-lock</d:getetag>"))
49+
50+
#expect(lock != nil)
51+
#expect(lock?.etag == "etag-after-lock")
52+
}
53+
}

0 commit comments

Comments
 (0)