diff --git a/test/v2/tests.ts/properties/.createFiles.ts b/test/v2/tests.ts/properties/.createFiles.ts
new file mode 100644
index 00000000..fc1498a5
--- /dev/null
+++ b/test/v2/tests.ts/properties/.createFiles.ts
@@ -0,0 +1,47 @@
+import { TestCallback, TestInfo } from '../Type'
+import { v2 } from '../../../../lib/index.js'
+
+export function proppatch(server : v2.WebDAVServer, info : TestInfo, path : string, expectedStatusCode : number, bodySet : string[], bodyRemove : string[], callback : (xml : v2.XMLElement) => void)
+{
+ let body = '';
+ if(bodySet && bodySet.length > 0)
+ body += '' + bodySet.join() + '';
+ if(bodyRemove && bodyRemove.length > 0)
+ body += '' + bodyRemove.join() + '';
+ body += '';
+
+ info.reqXML({
+ url: 'http://localhost:' + server.options.port + '/' + path,
+ method: 'PROPPATCH',
+ body
+ }, expectedStatusCode, (res, xml) => {
+ callback(xml);
+ })
+}
+
+export function propfind(server : v2.WebDAVServer, info : TestInfo, path : string, expectedStatusCode : number, depth : number, body : string, callback : (xml : v2.XMLElement) => void)
+{
+ info.reqXML({
+ url: 'http://localhost:' + server.options.port + '/' + path,
+ method: 'PROPFIND',
+ headers: {
+ depth
+ },
+ body
+ }, expectedStatusCode, (res, xml) => {
+ callback(xml);
+ })
+}
+
+export function starter(info : TestInfo, isValid : TestCallback, callback : (server : v2.WebDAVServer) => void) : void
+{
+ const server = info.startServer();
+ server.rootFileSystem().addSubTree(v2.RequestContext.createExternal(server), {
+ 'folder': v2.ResourceType.Directory,
+ 'file': v2.ResourceType.File
+ }, (e) => {
+ if(e) return isValid(false, 'Cannot call "addSubTree(...)".', e);
+
+ callback(server);
+ })
+}
diff --git a/test/v2/tests.ts/properties/.test.ts b/test/v2/tests.ts/properties/.test.ts
new file mode 100644
index 00000000..24491993
--- /dev/null
+++ b/test/v2/tests.ts/properties/.test.ts
@@ -0,0 +1,94 @@
+import { Test, TestInfo, TestCallback } from '../Type'
+import { v2 } from '../../../../lib/index.js'
+import { starter, propfind, proppatch } from './.createFiles'
+
+export function test(s : v2.WebDAVServer, info : TestInfo, isValid : TestCallback, path : string)
+{
+ proppatch(s, info, path, v2.HTTPCodes.MultiStatus, [
+ '',
+ '',
+ 'Ok',
+ '',
+ 'Ok'
+ ], null, (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+
+ const props = propstat.find('DAV:prop');
+ props.find('test1');
+ props.find('test2');
+ props.find('test3');
+ props.find('test4');
+ props.find('test5');
+
+ const value = propstat.find('DAV:status').findText();
+ if(value.indexOf(v2.HTTPCodes.OK.toString()) === -1)
+ return isValid(false, 'The status must be ' + v2.HTTPCodes.OK + ' but got : ' + value);
+
+ propfind(s, info, path, v2.HTTPCodes.MultiStatus, 0, undefined, (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+
+ const props = propstat.find('DAV:prop');
+ props.find('test1');
+ props.find('test2');
+
+ const test3 = props.find('test3');
+ const test4 = props.find('test4');
+ const test5 = props.find('test5');
+
+ let value = test3.findText();
+ if(value !== 'Ok')
+ return isValid(false, 'test3 does not have the right text ; exported "Ok" but got "' + value + '"');
+
+ value = test4.attributes['attribute'];
+ if(value !== 'Ok')
+ return isValid(false, 'test3 does not have the right attribute value ; exported "Ok" but got "' + value + '"');
+
+ value = test5.find('subtest5').findText();
+ if(value !== 'Ok')
+ return isValid(false, 'test5/subtest5 does not have the right text ; exported "Ok" but got "' + value + '"');
+
+ proppatch(s, info, path, v2.HTTPCodes.MultiStatus, [
+ 'Ok'
+ ], [ '' ], (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+
+ const props = propstat.find('DAV:prop');
+ props.find('test1');
+ props.find('test4');
+
+ const value = propstat.find('DAV:status').findText();
+ if(value.indexOf(v2.HTTPCodes.OK.toString()) === -1)
+ return isValid(false, 'The status must be ' + v2.HTTPCodes.OK + ' but got : ' + value);
+
+ propfind(s, info, path, v2.HTTPCodes.MultiStatus, 0, undefined, (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+
+ const props = propstat.find('DAV:prop');
+ props.find('test2');
+
+ const test1 = props.find('test1');
+ const test3 = props.find('test3');
+ const test5 = props.find('test5');
+
+ let value = test1.findText();
+ if(value !== 'Ok')
+ return isValid(false, 'test1 does not have the right text ; exported "Ok" but got "' + value + '"');
+
+ value = test3.findText();
+ if(value !== 'Ok')
+ return isValid(false, 'test3 does not have the right text ; exported "Ok" but got "' + value + '"');
+
+ value = test4.attributes['attribute'];
+ if(props.findIndex('test4') !== -1)
+ return isValid(false, 'test4 must be removed but it is still in the PROPFIND response');
+
+ value = test5.find('subtest5').findText();
+ if(value !== 'Ok')
+ return isValid(false, 'test5/subtest5 does not have the right text ; exported "Ok" but got "' + value + '"');
+
+ isValid(true);
+ })
+ })
+ })
+ })
+}
diff --git a/test/v2/tests.ts/properties/proppatchOnDirectory.ts b/test/v2/tests.ts/properties/proppatchOnDirectory.ts
new file mode 100644
index 00000000..442dbe94
--- /dev/null
+++ b/test/v2/tests.ts/properties/proppatchOnDirectory.ts
@@ -0,0 +1,14 @@
+import { Test } from '../Type'
+import { v2 } from '../../../../lib/index.js'
+import { starter } from './.createFiles'
+import { test } from './.test'
+
+export default ((info, isValid) =>
+{
+ info.init(1);
+
+ starter(info, isValid, (s) => {
+ test(s, info, isValid, 'folder');
+ });
+
+}) as Test;
diff --git a/test/v2/tests.ts/properties/proppatchOnFile.ts b/test/v2/tests.ts/properties/proppatchOnFile.ts
new file mode 100644
index 00000000..a8b11000
--- /dev/null
+++ b/test/v2/tests.ts/properties/proppatchOnFile.ts
@@ -0,0 +1,14 @@
+import { Test } from '../Type'
+import { v2 } from '../../../../lib/index.js'
+import { starter } from './.createFiles'
+import { test } from './.test'
+
+export default ((info, isValid) =>
+{
+ info.init(1);
+
+ starter(info, isValid, (s) => {
+ test(s, info, isValid, 'file');
+ });
+
+}) as Test;
diff --git a/test/v2/tests.ts/properties/proppatchOnUndefined.ts b/test/v2/tests.ts/properties/proppatchOnUndefined.ts
new file mode 100644
index 00000000..7703077a
--- /dev/null
+++ b/test/v2/tests.ts/properties/proppatchOnUndefined.ts
@@ -0,0 +1,30 @@
+import { Test } from '../Type'
+import { v2 } from '../../../../lib/index.js'
+import { starter, propfind, proppatch } from './.createFiles'
+
+export default ((info, isValid) =>
+{
+ info.init(2);
+
+ starter(info, isValid, (s) => {
+ proppatch(s, info, 'undefined', v2.HTTPCodes.NotFound, [ '' ], null, (xml) => {
+ isValid(true);
+ })
+ });
+
+ starter(info, isValid, (s) => {
+ proppatch(s, info, 'file', v2.HTTPCodes.MultiStatus, null, [ '' ], (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+ let value = propstat.find('DAV:prop').elements[0].name;
+ if(value !== 'test1')
+ return isValid(false, 'The element in the "prop" element must be "test1" but got : ' + value)
+
+ value = propstat.find('DAV:status').findText();
+ if(value.indexOf(v2.HTTPCodes.OK.toString()) === -1)
+ return isValid(false, 'The status must be ' + v2.HTTPCodes.OK + ' but got : ' + value);
+
+ isValid(true);
+ })
+ });
+
+}) as Test;
diff --git a/test/v2/tests.ts/properties/proppatchOnWebdavNamespace.ts b/test/v2/tests.ts/properties/proppatchOnWebdavNamespace.ts
new file mode 100644
index 00000000..301aecc5
--- /dev/null
+++ b/test/v2/tests.ts/properties/proppatchOnWebdavNamespace.ts
@@ -0,0 +1,54 @@
+import { Test } from '../Type'
+import { v2 } from '../../../../lib/index.js'
+import { starter, propfind, proppatch } from './.createFiles'
+
+export default ((info, isValid) =>
+{
+ info.init(3);
+
+ starter(info, isValid, (s) => {
+ proppatch(s, info, 'file', v2.HTTPCodes.MultiStatus, [ '"Value"' ], null, (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+ let value = propstat.find('DAV:prop').elements[0].name;
+ if(value !== 'DAV:getetag')
+ return isValid(false, 'The element in the "prop" element must be "getetag" but got : ' + value)
+
+ value = propstat.find('DAV:status').findText();
+ if(value.indexOf(v2.HTTPCodes.Forbidden.toString()) === -1)
+ return isValid(false, 'The status must be ' + v2.HTTPCodes.Forbidden + ' but got : ' + value);
+
+ isValid(true);
+ })
+ });
+
+ starter(info, isValid, (s) => {
+ proppatch(s, info, 'file', v2.HTTPCodes.MultiStatus, [ '"Value"' ], null, (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+ let value = propstat.find('DAV:prop').elements[0].name;
+ if(value !== 'DAV:getetagxx')
+ return isValid(false, 'The element in the "prop" element must be "getetagxx" but got : ' + value)
+
+ value = propstat.find('DAV:status').findText();
+ if(value.indexOf(v2.HTTPCodes.Forbidden.toString()) === -1)
+ return isValid(false, 'The status must be ' + v2.HTTPCodes.Forbidden + ' but got : ' + value);
+
+ isValid(true);
+ })
+ });
+
+ starter(info, isValid, (s) => {
+ proppatch(s, info, 'file', v2.HTTPCodes.MultiStatus, null, [ '"Value"' ], (xml) => {
+ const propstat = xml.find('DAV:multistatus').find('DAV:response').find('DAV:propstat')
+ let value = propstat.find('DAV:prop').elements[0].name;
+ if(value !== 'DAV:getetagxx')
+ return isValid(false, 'The element in the "prop" element must be "getetagxx" but got : ' + value)
+
+ value = propstat.find('DAV:status').findText();
+ if(value.indexOf(v2.HTTPCodes.Forbidden.toString()) === -1)
+ return isValid(false, 'The status must be ' + v2.HTTPCodes.Forbidden + ' but got : ' + value);
+
+ isValid(true);
+ })
+ });
+
+}) as Test;