# Exercise 2: Model Presigned URL Expiry and Prefix "Folder" Listing

```
STEP 1. Save the JavaScript below as `s3_model.js`.
STEP 2. Implement `isPresignedUrlValid(issuedAtEpochSeconds, expiresInSeconds, nowEpochSeconds)`:
          - Returns true only if now is BEFORE issuedAt + expiresIn.
          - At exactly the expiry instant, the URL is no longer valid
            (per theory section 8/9, expiry is enforced by comparing
            against the baked-in X-Amz-Expires, not "grace period").
STEP 3. Implement `listAsFolders(keys, prefix)`:
          - Given a flat array of object keys (theory section 3 — S3 has
            no real directories) and a prefix like "uploads/2024/",
            return the SORTED, DEDUPED list of immediate "child" names
            under that prefix — either a direct object's remaining key
            segment (no further "/"), or a "folder" name (segment up to
            and including the next "/") for keys that go deeper.
          - Example: keys ["uploads/2024/a.csv", "uploads/2024/b/c.csv"],
            prefix "uploads/2024/" -> ["a.csv", "b/"]
STEP 4. Run `node s3_model.js` and get every line to print PASS.
```

```js
// s3_model.js

function isPresignedUrlValid(issuedAtEpochSeconds, expiresInSeconds, nowEpochSeconds) {
  // TODO: implement per STEP 2
  return false;
}

function listAsFolders(keys, prefix) {
  // TODO: implement per STEP 3
  return [];
}

// --- self-check: do not modify below this line ---

const expiryTests = [
  { issuedAt: 1000, expiresIn: 300, now: 1200, expected: true, why: "200s elapsed, under the 300s window" },
  { issuedAt: 1000, expiresIn: 300, now: 1300, expected: false, why: "exactly at expiry instant -> no longer valid" },
  { issuedAt: 1000, expiresIn: 300, now: 1301, expected: false, why: "past expiry" },
  { issuedAt: 1000, expiresIn: 300, now: 999, expected: true, why: "before issuedAt is nonsensical input, but strictly 'not yet expired' holds" },
];

let failures = 0;
for (const t of expiryTests) {
  const result = isPresignedUrlValid(t.issuedAt, t.expiresIn, t.now);
  const pass = result === t.expected;
  if (!pass) failures++;
  console.log(`${pass ? "PASS" : "FAIL"} - isPresignedUrlValid(${t.issuedAt}, ${t.expiresIn}, ${t.now}) -> expected ${t.expected}, got ${result} (${t.why})`);
}

const listingTests = [
  {
    keys: ["uploads/2024/a.csv", "uploads/2024/b/c.csv", "uploads/2024/b/d.csv", "uploads/2023/old.csv"],
    prefix: "uploads/2024/",
    expected: ["a.csv", "b/"],
  },
  {
    keys: ["logs/app.log", "logs/2024/jan.log"],
    prefix: "logs/",
    expected: ["2024/", "app.log"],
  },
];

for (const t of listingTests) {
  const result = listAsFolders(t.keys, t.prefix);
  const pass = JSON.stringify(result) === JSON.stringify(t.expected);
  if (!pass) failures++;
  console.log(`${pass ? "PASS" : "FAIL"} - listAsFolders(prefix="${t.prefix}") -> expected ${JSON.stringify(t.expected)}, got ${JSON.stringify(result)}`);
}

console.log(failures === 0 ? "\nAll tests passed." : `\n${failures} test(s) failed.`);
```
