Copilot commented on code in PR #7831:
URL: https://github.com/apache/texera/pull/7831#discussion_r3835553682


##########
common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/model/LakeFSFileDocumentSpec.scala:
##########
@@ -37,6 +74,126 @@ class LakeFSFileDocumentSpec extends AnyFlatSpec with 
Matchers {
   // URI parsing is shared by every resource type; exercise it through the 
dataset type.
   private def datasetDoc(uri: URI) = new LakeFSFileDocument(uri, 
ResourceType.Dataset)
 
+  // 
-----------------------------------------------------------------------------------------
+  // Loopback stub
+  // 
-----------------------------------------------------------------------------------------
+
+  /** One recorded request. Fields are read directly; never destructured in a 
`case` pattern. */
+  private case class Hit(method: String, path: String, query: Map[String, 
String])
+
+  private val requests = new ConcurrentLinkedQueue[Hit]()
+  private var server: HttpServer = _
+  private var serverPool: ExecutorService = _
+
+  /** Request paths that should answer 500 instead of being served, for this 
one test. */
+  @volatile private var failing: Set[String] = Set.empty
+
+  private val repositoryName = "texera-file-doc"
+  private val fileName = "records.csv"
+  // Most dataset and model files live under a directory, so the read path is 
driven over both
+  // shapes: `fileName` at the repository root, and this nested path.
+  //
+  // getFileRelativePath() renders a parsed path with the *platform* 
separator, so every expectation
+  // over a multi-segment path — here and in the URI block above — is derived 
through Paths.get
+  // exactly as production renders it. Spelling out a literal 
"nested/dir/records.csv" instead would
+  // assert a normalization the class does not do (it would fail on Windows 
today), not a choice the
+  // class makes.
+  private val nestedSegments = Seq("nested", "dir", fileName)
+
+  private def statPath = 
s"/api/v1/repositories/$repositoryName/refs/$versionHash/objects/stat"
+  private def objectPath = 
s"/api/v1/repositories/$repositoryName/refs/$versionHash/objects"
+  private def presignedPath = "/signed-blob/records.csv"
+
+  // Longer than asFile's 1024-byte copy buffer and not a multiple of it (2502 
bytes), so the copy
+  // loop runs several times and ends on a short read that must not be padded 
out to a full buffer.
+  private val presignedContent: String = "presigned-payload;" * 139
+  // Deliberately different bytes, and a different length, from the presigned 
payload.
+  private val fallbackContent: String = "direct-lakefs-download"
+
+  private def presignedUrl: String =
+    s"http://127.0.0.1:${server.getAddress.getPort}$presignedPath";
+
+  private def objectStatsJson: String =
+    
s"""{"path":"$fileName","path_type":"object","physical_address":"$presignedUrl",
+       
|"checksum":"chk","mtime":1700000000,"size_bytes":${presignedContent.length}}""".stripMargin
+
+  private def handle(exchange: HttpExchange): Unit = {
+    try {
+      val uri = exchange.getRequestURI
+      val query = Option(uri.getRawQuery)
+        .filter(_.nonEmpty)
+        .map(_.split("&").toList.map { pair =>
+          def dec(s: String) = URLDecoder.decode(s, 
StandardCharsets.UTF_8.name())
+          pair.indexOf('=') match {
+            case -1 => dec(pair) -> ""
+            case i  => dec(pair.substring(0, i)) -> dec(pair.substring(i + 1))
+          }
+        }.toMap)
+        .getOrElse(Map.empty[String, String])
+      requests.add(Hit(exchange.getRequestMethod, uri.getPath, query))
+
+      val (status, body) =
+        if (failing.contains(uri.getPath)) (500, """{"message":"stubbed 
failure"}""")
+        else
+          uri.getPath match {
+            case p if p == statPath      => (200, objectStatsJson)
+            case p if p == presignedPath => (200, presignedContent)
+            case p if p == objectPath    => (200, fallbackContent)
+            case p                       => (501, s"""{"message":"no stub 
route for $p"}""")
+          }
+
+      val bytes = body.getBytes(StandardCharsets.UTF_8)
+      exchange.getResponseHeaders.set("Content-Type", "application/json")
+      exchange.sendResponseHeaders(status, bytes.length.toLong)
+      exchange.getResponseBody.write(bytes)

Review Comment:
   The loopback stub always sets `Content-Type: application/json`, but the 
`presignedPath` and direct `objects` download routes return raw bytes (not 
JSON). Using a binary content type makes the stub closer to real 
lakeFS/object-store responses and avoids accidentally relying on JSON content 
handling in the HTTP client.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to