Repository: solid/community-server (ts)

Issue:
rootfilepath need a / after the base 
according to https://github.com/solid/community-server/issues/444#issuecomment-751502319


i've tried a rootfilepath or more a "-f" but URL needs a / after the base
```
C:\Users\Smag\Documents\dev\community-server>npm start -- -p 5000 -c config/config-file.json -f data

> @solid/community-server@0.3.0 start C:\Users\Smag\Documents\dev\community-server
> node ./bin/server.js "-p" "5000" "-c" "config/config-file.json" "-f" "data"

2021-01-01T17:24:32.377Z [ExtensionBasedMapper] warn: URL http://localhost:5000 needs a / after the base
BadRequestHttpError: URL needs a / after the base

C:\Users\Smag\Documents\dev\community-server>npm start -- -p 5000 -c config/config-file.json -f data/

> @solid/community-server@0.3.0 start C:\Users\Smag\Documents\dev\community-server
> node ./bin/server.js "-p" "5000" "-c" "config/config-file.json" "-f" "data/"

2021-01-01T17:25:10.306Z [ExtensionBasedMapper] warn: URL http://localhost:5000 needs a / after the base
BadRequestHttpError: URL needs a / after the base

C:\Users\Smag\Documents\dev\community-server>npm start -- -p 5000 -c config/config-file.json -f /data/

> @solid/community-server@0.3.0 start C:\Users\Smag\Documents\dev\community-server
> node ./bin/server.js "-p" "5000" "-c" "config/config-file.json" "-f" "/data/"

2021-01-01T17:26:14.552Z [ExtensionBasedMapper] warn: URL http://localhost:5000 needs a / after the base
BadRequestHttpError: URL needs a / after the base

C:\Users\Smag\Documents\dev\community-server>
```

Pull request description:
fix: Don't get normalized metadata for root containers
Fixes #456 . As added bonus it also results in you being able to give a folder that doesn't exist as CLI param and it will be created (no nested folders though).

This still has the `hasSlash` variable there. Could have renamed it to something else but 2 lines below we are actually changing the slashes so it makes sense to me. The slash logic has to stay because it's part of the spec: 

> If two URIs differ only in the trailing slash, and the server has associated a resource with one of them, then the other URI MUST NOT correspond to another resource.

The only way to remove that logic from the store would be to move the entire function (or most parts of it) to the IdentifierStrategy but I think that makes less sense.

Patch:
diff --git a/src/storage/DataAccessorBasedStore.ts b/src/storage/DataAccessorBasedStore.ts
index baa0c7709..76e9040a8 100644
--- a/src/storage/DataAccessorBasedStore.ts
+++ b/src/storage/DataAccessorBasedStore.ts
@@ -185,7 +185,8 @@ export class DataAccessorBasedStore implements ResourceStore {
     try {
       return await this.accessor.getMetadata(identifier);
     } catch (error: unknown) {
-      if (error instanceof NotFoundHttpError) {
+      // Trimming the trailing slash of a root container is undefined as there is no parent container
+      if (error instanceof NotFoundHttpError && !this.identifierStrategy.isRootContainer(identifier)) {
         return this.accessor.getMetadata(
           { path: hasSlash ? trimTrailingSlashes(identifier.path) : ensureTrailingSlash(identifier.path) },
         );


Test patch:
diff --git a/test/unit/storage/DataAccessorBasedStore.test.ts b/test/unit/storage/DataAccessorBasedStore.test.ts
index f4848c313..a32eff880 100644
--- a/test/unit/storage/DataAccessorBasedStore.test.ts
+++ b/test/unit/storage/DataAccessorBasedStore.test.ts
@@ -241,6 +241,27 @@ describe('A DataAccessorBasedStore', (): void => {
         .rejects.toThrow(`${resourceID.path} conflicts with existing path ${resourceID.path}/`);
     });
 
+    // As discussed in #475, trimming the trailing slash of a root container in getNormalizedMetadata
+    // can result in undefined behaviour since there is no parent container.
+    it('will not trim the slash of root containers since there is no parent.', async(): Promise<void> => {
+      // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+      delete accessor.data[root];
+
+      const mock = jest.spyOn(accessor, 'getMetadata');
+
+      const resourceID = { path: `${root}` };
+      representation.metadata.removeAll(RDF.type);
+      representation.metadata.contentType = 'text/turtle';
+      representation.data = guardedStreamFrom([ `<${`${root}`}> a <coolContainer>.` ]);
+
+      await expect(store.setRepresentation(resourceID, representation))
+        .resolves.toBeUndefined();
+      expect(mock).toHaveBeenCalledTimes(1);
+      expect(mock).toHaveBeenLastCalledWith(resourceID);
+
+      mock.mockRestore();
+    });
+
     it('will error if the target has a different resource type.', async(): Promise<void> => {
       const resourceID = { path: `${root}resource` };
       accessor.data[resourceID.path] = representation;