mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-06-19 09:24:23 +08:00
The credit limit error messages had several i18n issues:
1. `relative_reset_time` prepended a hardcoded "in " prefix that
could not be translated, and when combined with locale strings
like "Please try again after %{reset_time}" produced the
grammatically incorrect "after in 9h"
2. `playground.rb` was still using `relative_reset_time` instead of
`formatted_reset_time`, unlike the rest of the codebase which had
already been updated
3. `formatted_reset_time` used a raw `strftime` with hardcoded
English ("on", abbreviated month names) instead of `I18n.l`
4. The shared AI conversations page also used `strftime` with
English month names for the conversation date
This commit:
- Switches `playground.rb` to use `formatted_reset_time`, matching
`ai_credit_limit_handler` and the streaming jobs
- Removes the hardcoded "in " prefix from `relative_reset_time` so
the duration string is usable as a translatable fallback client-side
- Replaces `strftime` in `formatted_reset_time` with
`I18n.l(next_reset_at, format: :long)` so the date/time format is
fully locale-aware
- Replaces `strftime` in the shared conversations view with
`I18n.l(..., format: :date_only)` for the same reason
https://meta.discourse.org/t/393773
168 lines
4.7 KiB
JavaScript
Vendored
168 lines
4.7 KiB
JavaScript
Vendored
import { getOwner } from "@ember/owner";
|
|
import { setupTest } from "ember-qunit";
|
|
import { module, test } from "qunit";
|
|
import sinon from "sinon";
|
|
import {
|
|
isAiCreditLimitError,
|
|
popupAiCreditLimitError,
|
|
} from "discourse/plugins/discourse-ai/discourse/lib/ai-errors";
|
|
|
|
module("Unit | Utility | ai-errors", function (hooks) {
|
|
setupTest(hooks);
|
|
|
|
module("isAiCreditLimitError", function () {
|
|
test("detects AJAX error format from controller", function (assert) {
|
|
const error = {
|
|
jqXHR: {
|
|
responseJSON: {
|
|
error: "credit_limit_exceeded",
|
|
},
|
|
},
|
|
};
|
|
|
|
assert.true(
|
|
isAiCreditLimitError(error),
|
|
"Should detect controller error format"
|
|
);
|
|
});
|
|
|
|
test("detects MessageBus payload format from streaming job", function (assert) {
|
|
const payload = {
|
|
error_type: "credit_limit_exceeded",
|
|
message: "Credit limit exceeded",
|
|
details: {},
|
|
};
|
|
|
|
assert.true(
|
|
isAiCreditLimitError(payload),
|
|
"Should detect streaming job format"
|
|
);
|
|
});
|
|
|
|
test("detects direct error object format", function (assert) {
|
|
const error = {
|
|
error: "credit_limit_exceeded",
|
|
};
|
|
|
|
assert.true(
|
|
isAiCreditLimitError(error),
|
|
"Should detect direct error format"
|
|
);
|
|
});
|
|
|
|
test("returns false for non-credit-limit errors", function (assert) {
|
|
const error = {
|
|
jqXHR: {
|
|
responseJSON: {
|
|
error: "some_other_error",
|
|
},
|
|
},
|
|
};
|
|
|
|
assert.false(
|
|
isAiCreditLimitError(error),
|
|
"Should not detect other errors"
|
|
);
|
|
});
|
|
|
|
test("returns false for unrelated objects", function (assert) {
|
|
assert.false(
|
|
isAiCreditLimitError({}),
|
|
"Should return false for empty object"
|
|
);
|
|
assert.false(isAiCreditLimitError(null), "Should return false for null");
|
|
assert.false(
|
|
isAiCreditLimitError(undefined),
|
|
"Should return false for undefined"
|
|
);
|
|
});
|
|
});
|
|
|
|
module("popupAiCreditLimitError", function () {
|
|
test("shows dialog with reset time when available", function (assert) {
|
|
const dialogService = getOwner(this).lookup("service:dialog");
|
|
const alertStub = sinon.stub(dialogService, "alert");
|
|
|
|
const error = {
|
|
jqXHR: {
|
|
responseJSON: {
|
|
error: "credit_limit_exceeded",
|
|
details: {
|
|
reset_time_absolute: "5:40pm on Dec 25, 2024",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
popupAiCreditLimitError(error);
|
|
|
|
assert.true(alertStub.calledOnce, "Dialog should be shown");
|
|
const callArgs = alertStub.firstCall.args[0];
|
|
// Convert htmlSafe string to regular string for testing
|
|
const messageStr = callArgs.message.toString();
|
|
assert.true(
|
|
messageStr.includes("5:40pm on Dec 25, 2024"),
|
|
"Message should include reset time"
|
|
);
|
|
assert.strictEqual(
|
|
callArgs.title,
|
|
"AI credit limit reached",
|
|
"Title should be correct"
|
|
);
|
|
|
|
alertStub.restore();
|
|
});
|
|
|
|
test("shows dialog without reset time when unavailable", function (assert) {
|
|
const dialogService = getOwner(this).lookup("service:dialog");
|
|
const alertStub = sinon.stub(dialogService, "alert");
|
|
|
|
const error = {
|
|
error_type: "credit_limit_exceeded",
|
|
details: {},
|
|
};
|
|
|
|
popupAiCreditLimitError(error);
|
|
|
|
assert.true(alertStub.calledOnce, "Dialog should be shown");
|
|
const callArgs = alertStub.firstCall.args[0];
|
|
// Convert htmlSafe string to regular string for testing
|
|
const messageStr = callArgs.message.toString();
|
|
assert.false(
|
|
/\bat\b\s+\d/.test(messageStr),
|
|
"Message should not include time formatted as 'at [time]'"
|
|
);
|
|
assert.true(
|
|
messageStr.includes("until your limit resets"),
|
|
"Message should mention reset"
|
|
);
|
|
|
|
alertStub.restore();
|
|
});
|
|
|
|
test("handles MessageBus payload format", function (assert) {
|
|
const dialogService = getOwner(this).lookup("service:dialog");
|
|
const alertStub = sinon.stub(dialogService, "alert");
|
|
|
|
const payload = {
|
|
error_type: "credit_limit_exceeded",
|
|
details: {
|
|
reset_time_relative: "2h",
|
|
},
|
|
};
|
|
|
|
popupAiCreditLimitError(payload);
|
|
|
|
assert.true(alertStub.calledOnce, "Dialog should be shown");
|
|
const callArgs = alertStub.firstCall.args[0];
|
|
// Convert htmlSafe string to regular string for testing
|
|
const messageStr = callArgs.message.toString();
|
|
assert.true(
|
|
messageStr.includes("2h"),
|
|
"Message should include relative time"
|
|
);
|
|
|
|
alertStub.restore();
|
|
});
|
|
});
|
|
});
|