mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
0f60fbfbdd
* fix(slides): relax office token length check from 28 to >=25 The interleaved "OFL0X" product/region marker is read at fixed positions (1-based 5/10/15/20/25), so a token only has to be long enough to hold it. Pinning the total length to exactly 28 silently reclassified every other length as native. 28 is already stale: per #2509 the local-office format is "OFL0X + 21 random + 1 office type enum" = 27 characters, and sheets relaxed the identical guard to >= 25 in that PR. Slides was missed, so an imported office deck at the current length uploaded with parent_type "slide_file" instead of "office_slide_file". The marker positions are unchanged. Relaxing the length is only safe because of them: a false positive is the dangerous direction, since the drive backend does not validate that parent_node actually names an office file, so a misclassified native deck uploads successfully and only surfaces later as an image that will not render. The interleaved native token cases (same length, different marker) are what keep the floor honest. Tests: replaced two mislabelled rows with five verified ones covering 27, 29, 25, 24 characters and a 28-character token carrying the ppt office type enum. #2509's own labels were off by one or two characters and it never covered the 25/24 boundary; this does. * refactor(common): extract local-office token detection into common The office token shape existed in three identical copies: shortcuts/sheets/helpers.go, shortcuts/sheets/backward, and shortcuts/slides. Every copy is somewhere a format change has to be found again, and that is not hypothetical — #2509 had to apply the same 28-to->=25 relaxation twice inside sheets, and missed slides entirely. Moved the shape to common.IsLocalOfficeToken. It belongs there because recognising a local-office document is a drive-level property, not a per-domain one: an imported office file is an imported office file whether it backs a spreadsheet or a deck. What genuinely differs per domain is the parent_type the answer selects — office_sheet_file vs office_slide_file — so those mappings stay with each domain. The name deliberately matches the vocabulary #2509 already used ("local-office format"). Its doc comment calls out that "local office" is the whole category and not the LocalOfficeTokenPrefix case, since the two now share a word stem while the predicate also accepts FakeOfficeTokenPrefix and the interleaved marker. Only slides is rewired here. The two sheets copies are left alone on purpose to keep this reviewable as a pure no-op for them; they can follow separately. The marker offsets are now an array whose length is tied to the marker string, so adding a character to one without the other stops compiling, and TestOfficeTokenMinLenMatchesMarkerOffsets pins the length floor to one past the last offset rather than letting the two merely agree by coincidence. Behaviour is unchanged, verified by diffing dry-run parent_type between the pre-refactor and post-refactor binaries across 13 tokens covering both prefixes, the 24/25 boundary, 27/28/29 characters, interleaved native pptcn/shtcn markers, a leading-but-misaligned OFL0X, and an off-by-one offset: 13/13 identical. * refactor(sheets): route local-office detection through common Deletes the last two copies of the token shape, both byte-identical to the one now in common: shortcuts/sheets/helpers.go and shortcuts/sheets/backward/lark_sheets_float_images.go. sheetMediaParentType keeps owning the sheets half of the decision — which parent_type the answer selects — and only the shape moves. Equivalence was not assumed from reading. A throwaway fuzz test compared isOfficeSpreadsheet against common.IsLocalOfficeToken in both packages over an alphabet biased toward the characters that can actually disagree (OFL0X plus the native product markers), every single-byte mutation of a known office token at all 28 positions, and 400k fixed-seed random tokens of length 0-33. Zero disagreements in either package. Dry-run parent_type was then diffed binary-to-binary against origin/main across 11 tokens covering the 24/25 boundary, 27/28/29 characters, interleaved native shtcn/pptcn markers, a leading-but-misaligned OFL0X and an off-by-one offset: sheets identical on all 11. Two comment fixes that the extraction made unavoidable: The const-block doc in both files still described "a 28-character token". That was already wrong on main — #2509 relaxed the guard to >= 25 and left the comment behind — and the shape is no longer described here at all now, so both defer to common.IsLocalOfficeToken. Four rows in TestSheetMediaParentType were mislabelled: "25 char, at boundary" held a 27-character token and the three "new 27-char" rows held 28-character ones, so the floor those labels claimed to cover was never tested. Relabelled by measured length, and the real 25/24 boundary added.
68 lines
3.0 KiB
Go
68 lines
3.0 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package common
|
|
|
|
import "testing"
|
|
|
|
// TestIsLocalOfficeToken pins the shared token shape every domain's
|
|
// parent_type mapping now reads through.
|
|
//
|
|
// The negative cases carry as much weight as the positive ones. A native token
|
|
// read as office still uploads successfully — the drive backend does not
|
|
// validate that parent_node names an office file — so the failure only shows up
|
|
// later as an image that will not render. The interleaved native tokens are what
|
|
// make the length floor safe: they are long enough to be read but carry a
|
|
// different marker, so relaxing the length must not pull them in.
|
|
func TestIsLocalOfficeToken(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
name string
|
|
token string
|
|
want bool
|
|
}{
|
|
{"empty token", "", false},
|
|
{"native token, too short to read", "pptcnABC123", false},
|
|
|
|
{"fake_office prefix", "fake_office_abc123", true},
|
|
{"fake_office, only the prefix", FakeOfficeTokenPrefix, true},
|
|
{"local_office prefix", "local_office_abc123", true},
|
|
{"local_office, only the prefix", LocalOfficeTokenPrefix, true},
|
|
{"fake_office prefix mid-string is not a prefix", "pptfake_office_abc", false},
|
|
{"local_office prefix mid-string is not a prefix", "pptlocal_office_abc", false},
|
|
|
|
{"interleaved OFL0X, 25 chars (marker exactly fills the token)", "aaaaOaaaaFaaaaLaaaa0aaaaX", true},
|
|
{"interleaved OFL0X, 27 chars (current local-office format)", "aaaaOaaaaFaaaaLaaaa0aaaaXaa", true},
|
|
{"interleaved OFL0X, 28 chars (the length that used to be pinned)", "aaaaOaaaaFaaaaLaaaa0aaaaXaaa", true},
|
|
{"interleaved OFL0X, 28 chars with ppt office-type enum", "ccccOccccFccccLcccc0ccccXccP", true},
|
|
{"interleaved OFL0X, 29 chars (longer than any known format)", "aaaaOaaaaFaaaaLaaaa0aaaaXaaaa", true},
|
|
{"interleaved OFL0X, 24 chars (one short of holding the marker)", "aaaaOaaaaFaaaaLaaaa0aaaa", false},
|
|
|
|
{"interleaved pptcn native token", "abcdpefghpijkltmnopcqrstnuv", false},
|
|
{"interleaved shtcn native token", "abcdsefghhijkltmnopcqrstnuv", false},
|
|
{"OFL0X present but not on the offsets", "OFL0Xaaaaaaaaaaaaaaaaaaaaaaa", false},
|
|
{"marker off by one offset", "aaaaaOaaaaFaaaaLaaaa0aaaaX", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := IsLocalOfficeToken(tc.token); got != tc.want {
|
|
t.Fatalf("IsLocalOfficeToken(%q) = %v, want %v", tc.token, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestOfficeTokenMinLenMatchesMarkerOffsets keeps the length floor derived from
|
|
// the offsets rather than merely agreeing with them today. A floor that drifts
|
|
// above the last offset would reject valid short tokens; one that drifts below
|
|
// it would index out of range.
|
|
func TestOfficeTokenMinLenMatchesMarkerOffsets(t *testing.T) {
|
|
t.Parallel()
|
|
last := officeTokenMarkerOffsets[len(officeTokenMarkerOffsets)-1]
|
|
if officeTokenMinLen != last+1 {
|
|
t.Fatalf("officeTokenMinLen = %d, want %d (one past last marker offset %d)",
|
|
officeTokenMinLen, last+1, last)
|
|
}
|
|
}
|