From 1c1993f972829b862af72216953218a3b59d1f58 Mon Sep 17 00:00:00 2001 From: tdurieux Date: Thu, 2 Jul 2026 14:53:26 +0300 Subject: [PATCH] fix: try to fix gist --- src/core/Gist.ts | 3 - .../anonymizedGists/anonymizedGists.schema.ts | 5 +- test/gist-schema.test.js | 67 +++++++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 test/gist-schema.test.js diff --git a/src/core/Gist.ts b/src/core/Gist.ts index 57e09f0..ea19a88 100644 --- a/src/core/Gist.ts +++ b/src/core/Gist.ts @@ -114,9 +114,6 @@ export default class Gist { comments: commentsMapped, }; this._gistPayload = payload; - // `gist` is a nested path (not a sub-schema), so assigning the whole - // object via set("gist", payload) mis-casts the inner subdoc arrays. - // Set each sub-path individually so Mongoose casts arrays correctly. this._model.set("gist.description", payload.description); this._model.set("gist.isPublic", payload.isPublic); this._model.set("gist.creationDate", payload.creationDate); diff --git a/src/core/model/anonymizedGists/anonymizedGists.schema.ts b/src/core/model/anonymizedGists/anonymizedGists.schema.ts index e49c4f1..b5b212d 100644 --- a/src/core/model/anonymizedGists/anonymizedGists.schema.ts +++ b/src/core/model/anonymizedGists/anonymizedGists.schema.ts @@ -51,7 +51,10 @@ const AnonymizedGistSchema = new Schema({ content: String, language: String, size: Number, - type: String, + // `type` is a reserved key in Mongoose type declarations; without the + // nested `{ type: String }` the whole object is compiled as an array + // of strings and file objects are silently dropped on save. + type: { type: String }, }, ], comments: [ diff --git a/test/gist-schema.test.js b/test/gist-schema.test.js new file mode 100644 index 0000000..5861d41 --- /dev/null +++ b/test/gist-schema.test.js @@ -0,0 +1,67 @@ +const { expect } = require("chai"); +require("ts-node/register/transpile-only"); +const mongoose = require("mongoose"); +const AnonymizedGistSchema = require("../src/core/model/anonymizedGists/anonymizedGists.schema").default; + +/** + * Regression test for the empty gist page bug. + * + * The `files` sub-document declared a data field literally named `type` + * (`type: String`). Mongoose treats a bare `type` key as a type + * declaration, so `gist.files` was compiled as an array of *strings* + * instead of sub-documents. Every downloaded file object then failed + * casting and was silently dropped, and gists were saved with + * `gist.files: []` — rendering an empty gist page. + */ + +describe("AnonymizedGistSchema gist.files", function () { + const Model = + mongoose.models.GistSchemaTest || + mongoose.model("GistSchemaTest", AnonymizedGistSchema); + + const files = [ + { + filename: "a.txt", + content: "hello world", + language: "Text", + size: 11, + type: "text/plain", + }, + { + filename: "b.md", + content: "# readme", + language: "Markdown", + size: 8, + type: "text/markdown", + }, + ]; + + it("compiles gist.files as a sub-document array, not a string array", function () { + const path = AnonymizedGistSchema.path("gist.files"); + expect(path.constructor.name).to.equal("DocumentArrayPath"); + }); + + it("keeps file objects when set via sub-paths (download() flow)", function () { + const doc = new Model({}); + doc.set("gist.files", files); + expect(doc.gist.files).to.have.length(2); + expect(doc.gist.files[0].filename).to.equal("a.txt"); + expect(doc.gist.files[0].content).to.equal("hello world"); + expect(doc.gist.files[0].type).to.equal("text/plain"); + }); + + it("keeps file objects when passed to the constructor", function () { + const doc = new Model({ gist: { files } }); + expect(doc.gist.files).to.have.length(2); + expect(doc.gist.files[1].size).to.equal(8); + }); + + it("casts gist.files correctly in an updateOne $set (updateIfNeeded flow)", function () { + const doc = new Model({}); + doc.set("gist.files", files); + const query = Model.updateOne({ _id: doc._id }, { $set: { gist: doc.gist } }); + const casted = query._update.$set.gist; + expect(casted.files).to.have.length(2); + expect(casted.files[0].content).to.equal("hello world"); + }); +});